diff options
56 files changed, 16170 insertions, 3 deletions
diff --git a/build_opts/ALPHA b/build_opts/ALPHA index 384394091..4c327fa00 100644 --- a/build_opts/ALPHA +++ b/build_opts/ALPHA @@ -1,4 +1,4 @@ TARGET_ISA = 'alpha' SS_COMPATIBLE_FP = 1 -CPU_MODELS = 'AtomicSimpleCPU,TimingSimpleCPU,O3CPU,InOrderCPU' +CPU_MODELS = 'AtomicSimpleCPU,TimingSimpleCPU,O3CPU,InOrderCPU,MinorCPU' PROTOCOL = 'MI_example' diff --git a/build_opts/ARM b/build_opts/ARM index 9c27c0d27..b175fd0ad 100644 --- a/build_opts/ARM +++ b/build_opts/ARM @@ -1,3 +1,3 @@ TARGET_ISA = 'arm' -CPU_MODELS = 'AtomicSimpleCPU,TimingSimpleCPU,O3CPU' +CPU_MODELS = 'AtomicSimpleCPU,TimingSimpleCPU,O3CPU,MinorCPU' PROTOCOL = 'MI_example' diff --git a/configs/common/CpuConfig.py b/configs/common/CpuConfig.py index e12b646d8..c2663ee98 100644 --- a/configs/common/CpuConfig.py +++ b/configs/common/CpuConfig.py @@ -51,6 +51,7 @@ _cpu_aliases_all = [ ("timing", "TimingSimpleCPU"), ("atomic", "AtomicSimpleCPU"), ("inorder", "InOrderCPU"), + ("minor", "MinorCPU"), ("detailed", "DerivO3CPU"), ("kvm", ("ArmKvmCPU", "X86KvmCPU")), ] diff --git a/src/base/trace.hh b/src/base/trace.hh index dbeffdc8b..eb0ab9dae 100644 --- a/src/base/trace.hh +++ b/src/base/trace.hh @@ -72,6 +72,20 @@ struct StringWrap inline const std::string &name() { return Trace::DefaultName; } +// Interface for things with names. (cf. SimObject but without other +// functionality). This is useful when using DPRINTF +class Named +{ + protected: + const std::string _name; + + public: + Named(const std::string &name_) : _name(name_) { } + + public: + const std::string &name() const { return _name; } +}; + // // DPRINTF is a debugging trace facility that allows one to // selectively enable tracing statements. To use DPRINTF, there must diff --git a/src/cpu/SConscript b/src/cpu/SConscript index ca9c6a791..1ea92114a 100644 --- a/src/cpu/SConscript +++ b/src/cpu/SConscript @@ -106,6 +106,7 @@ SimObject('ExeTracer.py') SimObject('IntelTrace.py') SimObject('IntrControl.py') SimObject('NativeTrace.py') +SimObject('TimingExpr.py') Source('activity.cc') Source('base.cc') @@ -123,6 +124,7 @@ Source('static_inst.cc') Source('simple_thread.cc') Source('thread_context.cc') Source('thread_state.cc') +Source('timing_expr.cc') if env['TARGET_ISA'] == 'sparc': SimObject('LegionTrace.py') diff --git a/src/cpu/TimingExpr.py b/src/cpu/TimingExpr.py new file mode 100644 index 000000000..6a9d6f95c --- /dev/null +++ b/src/cpu/TimingExpr.py @@ -0,0 +1,176 @@ +# Copyright (c) 2013-2014 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: Andrew Bardsley + +from m5.params import * +from m5.SimObject import SimObject + +# These classes define an expression language over uint64_t with only +# a few operators. This can be used to form expressions for the extra +# delay required in variable execution time instructions. +# +# Expressions, in evaluation, will have access to the ThreadContext and +# a StaticInst + +class TimingExpr(SimObject): + type = 'TimingExpr' + cxx_header = 'cpu/timing_expr.hh' + abstract = True; + +class TimingExprLiteral(TimingExpr): + """Literal 64 bit unsigned value""" + type = 'TimingExprLiteral' + cxx_header = 'cpu/timing_expr.hh' + + value = Param.UInt64("literal value") + + def set_params(self, value): + self.value = value + return self + +class TimingExpr0(TimingExprLiteral): + """Convenient 0""" + value = 0 + +class TimingExprSrcReg(TimingExpr): + """Find the source register number from the current inst""" + type = 'TimingExprSrcReg' + cxx_header = 'cpu/timing_expr.hh' + + # index = Param.Unsigned("index into inst src regs") + index = Param.Unsigned("index into inst src regs") + + def set_params(self, index): + self.index = index + return self + +class TimingExprReadIntReg(TimingExpr): + """Read an architectural register""" + type = 'TimingExprReadIntReg' + cxx_header = 'cpu/timing_expr.hh' + + reg = Param.TimingExpr("register raw index to read") + + def set_params(self, reg): + self.reg = reg + return self + +class TimingExprLet(TimingExpr): + """Block of declarations""" + type = 'TimingExprLet' + cxx_header = 'cpu/timing_expr.hh' + + defns = VectorParam.TimingExpr("expressions for bindings") + expr = Param.TimingExpr("body expression") + + def set_params(self, defns, expr): + self.defns = defns + self.expr = expr + return self + +class TimingExprRef(TimingExpr): + """Value of a bound sub-expression""" + type = 'TimingExprRef' + cxx_header = 'cpu/timing_expr.hh' + + index = Param.Unsigned("expression index") + + def set_params(self, index): + self.index = index + return self + +class TimingExprOp(Enum): + vals = [ + 'timingExprAdd', 'timingExprSub', + 'timingExprUMul', 'timingExprUDiv', + 'timingExprSMul', 'timingExprSDiv', + 'timingExprUCeilDiv', # Unsigned divide rounding up + 'timingExprEqual', 'timingExprNotEqual', + 'timingExprULessThan', + 'timingExprUGreaterThan', + 'timingExprSLessThan', + 'timingExprSGreaterThan', + 'timingExprInvert', + 'timingExprNot', + 'timingExprAnd', + 'timingExprOr', + 'timingExprSizeInBits', + 'timingExprSignExtend32To64', + 'timingExprAbs' + ] + +class TimingExprUn(TimingExpr): + """Unary operator""" + type = 'TimingExprUn' + cxx_header = 'cpu/timing_expr.hh' + + op = Param.TimingExprOp("operator") + arg = Param.TimingExpr("expression") + + def set_params(self, op, arg): + self.op = op + self.arg = arg + return self + +class TimingExprBin(TimingExpr): + """Binary operator""" + type = 'TimingExprBin' + cxx_header = 'cpu/timing_expr.hh' + + op = Param.TimingExprOp("operator") + left = Param.TimingExpr("LHS expression") + right = Param.TimingExpr("RHS expression") + + def set_params(self, op, left, right): + self.op = op + self.left = left + self.right = right + return self + +class TimingExprIf(TimingExpr): + """If-then-else operator""" + type = 'TimingExprIf' + cxx_header = 'cpu/timing_expr.hh' + + cond = Param.TimingExpr("condition expression") + trueExpr = Param.TimingExpr("true expression") + falseExpr = Param.TimingExpr("false expression") + + def set_params(self, cond, trueExpr, falseExpr): + self.cond = cond + self.trueExpr = trueExpr + self.falseExpr = falseExpr + return self diff --git a/src/cpu/minor/MinorCPU.py b/src/cpu/minor/MinorCPU.py new file mode 100644 index 000000000..07953cf5a --- /dev/null +++ b/src/cpu/minor/MinorCPU.py @@ -0,0 +1,274 @@ +# Copyright (c) 2012-2014 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) 2007 The Regents of The University of Michigan +# 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. +# +# Authors: Gabe Black +# Nathan Binkert +# Andrew Bardsley + +from m5.defines import buildEnv +from m5.params import * +from m5.proxy import * +from m5.SimObject import SimObject +from BaseCPU import BaseCPU +from DummyChecker import DummyChecker +from BranchPredictor import BranchPredictor +from TimingExpr import TimingExpr + +from FuncUnit import OpClass + +class MinorOpClass(SimObject): + """Boxing of OpClass to get around build problems and provide a hook for + future additions to OpClass checks""" + + type = 'MinorOpClass' + cxx_header = "cpu/minor/func_unit.hh" + + opClass = Param.OpClass("op class to match") + +class MinorOpClassSet(SimObject): + """A set of matchable op classes""" + + type = 'MinorOpClassSet' + cxx_header = "cpu/minor/func_unit.hh" + + opClasses = VectorParam.MinorOpClass([], "op classes to be matched." + " An empty list means any class") + +class MinorFUTiming(SimObject): + type = 'MinorFUTiming' + cxx_header = "cpu/minor/func_unit.hh" + + mask = Param.UInt64(0, "mask for testing ExtMachInst") + match = Param.UInt64(0, "match value for testing ExtMachInst:" + " (ext_mach_inst & mask) == match") + suppress = Param.Bool(False, "if true, this inst. is not executed by" + " this FU") + extraCommitLat = Param.Cycles(0, "extra cycles to stall commit for" + " this inst.") + extraCommitLatExpr = Param.TimingExpr(NULL, "extra cycles as a" + " run-time evaluated expression") + extraAssumedLat = Param.Cycles(0, "extra cycles to add to scoreboard" + " retire time for this insts dest registers once it leaves the" + " functional unit. For mem refs, if this is 0, the result's time" + " is marked as unpredictable and no forwarding can take place.") + srcRegsRelativeLats = VectorParam.Cycles("the maximum number of cycles" + " after inst. issue that each src reg can be available for this" + " inst. to issue") + opClasses = Param.MinorOpClassSet(MinorOpClassSet(), + "op classes to be considered for this decode. An empty set means any" + " class") + description = Param.String('', "description string of the decoding/inst." + " class") + +def minorMakeOpClassSet(op_classes): + """Make a MinorOpClassSet from a list of OpClass enum value strings""" + def boxOpClass(op_class): + return MinorOpClass(opClass=op_class) + + return MinorOpClassSet(opClasses=map(boxOpClass, op_classes)) + +class MinorFU(SimObject): + type = 'MinorFU' + cxx_header = "cpu/minor/func_unit.hh" + + opClasses = Param.MinorOpClassSet(MinorOpClassSet(), "type of operations" + " allowed on this functional unit") + opLat = Param.Cycles(1, "latency in cycles") + issueLat = Param.Cycles(1, "cycles until another instruction can be" + " issued") + timings = VectorParam.MinorFUTiming([], "extra decoding rules") + + cantForwardFromFUIndices = VectorParam.Unsigned([], + "list of FU indices from which this FU can't receive and early" + " (forwarded) result") + +class MinorFUPool(SimObject): + type = 'MinorFUPool' + cxx_header = "cpu/minor/func_unit.hh" + + funcUnits = VectorParam.MinorFU("functional units") + +class MinorDefaultIntFU(MinorFU): + opClasses = minorMakeOpClassSet(['IntAlu']) + timings = [MinorFUTiming(description="Int", + srcRegsRelativeLats=[2])] + opLat = 3 + +class MinorDefaultIntMulFU(MinorFU): + opClasses = minorMakeOpClassSet(['IntMult']) + timings = [MinorFUTiming(description='Mul', + srcRegsRelativeLats=[0])] + opLat = 3 + +class MinorDefaultIntDivFU(MinorFU): + opClasses = minorMakeOpClassSet(['IntDiv']) + issueLat = 9 + opLat = 9 + +class MinorDefaultFloatSimdFU(MinorFU): + opClasses = minorMakeOpClassSet([ + 'FloatAdd', 'FloatCmp', 'FloatCvt', 'FloatMult', 'FloatDiv', + 'FloatSqrt', + 'SimdAdd', 'SimdAddAcc', 'SimdAlu', 'SimdCmp', 'SimdCvt', + 'SimdMisc', 'SimdMult', 'SimdMultAcc', 'SimdShift', 'SimdShiftAcc', + 'SimdSqrt', 'SimdFloatAdd', 'SimdFloatAlu', 'SimdFloatCmp', + 'SimdFloatCvt', 'SimdFloatDiv', 'SimdFloatMisc', 'SimdFloatMult', + 'SimdFloatMultAcc', 'SimdFloatSqrt']) + timings = [MinorFUTiming(description='FloatSimd', + srcRegsRelativeLats=[2])] + opLat = 6 + +class MinorDefaultMemFU(MinorFU): + opClasses = minorMakeOpClassSet(['MemRead', 'MemWrite']) + timings = [MinorFUTiming(description='Mem', + srcRegsRelativeLats=[1], extraAssumedLat=2)] + opLat = 1 + +class MinorDefaultMiscFU(MinorFU): + opClasses = minorMakeOpClassSet(['IprAccess', 'InstPrefetch']) + opLat = 1 + +class MinorDefaultFUPool(MinorFUPool): + funcUnits = [MinorDefaultIntFU(), MinorDefaultIntFU(), + MinorDefaultIntMulFU(), MinorDefaultIntDivFU(), + MinorDefaultFloatSimdFU(), MinorDefaultMemFU(), + MinorDefaultMiscFU()] + +class MinorCPU(BaseCPU): + type = 'MinorCPU' + cxx_header = "cpu/minor/cpu.hh" + + @classmethod + def memory_mode(cls): + return 'timing' + + @classmethod + def require_caches(cls): + return True + + @classmethod + def support_take_over(cls): + return True + + fetch1FetchLimit = Param.Unsigned(1, + "Number of line fetches allowable in flight at once") + fetch1LineSnapWidth = Param.Unsigned(0, + "Fetch1 'line' fetch snap size in bytes" + " (0 means use system cache line size)") + fetch1LineWidth = Param.Unsigned(0, + "Fetch1 maximum fetch size in bytes (0 means use system cache" + " line size)") + fetch1ToFetch2ForwardDelay = Param.Cycles(1, + "Forward cycle delay from Fetch1 to Fetch2 (1 means next cycle)") + fetch1ToFetch2BackwardDelay = Param.Cycles(1, + "Backward cycle delay from Fetch2 to Fetch1 for branch prediction" + " signalling (0 means in the same cycle, 1 mean the next cycle)") + + fetch2InputBufferSize = Param.Unsigned(2, + "Size of input buffer to Fetch2 in cycles-worth of insts.") + fetch2ToDecodeForwardDelay = Param.Cycles(1, + "Forward cycle delay from Fetch2 to Decode (1 means next cycle)") + fetch2CycleInput = Param.Bool(True, + "Allow Fetch2 to cross input lines to generate full output each" + " cycle") + + decodeInputBufferSize = Param.Unsigned(3, + "Size of input buffer to Decode in cycles-worth of insts.") + decodeToExecuteForwardDelay = Param.Cycles(1, + "Forward cycle delay from Decode to Execute (1 means next cycle)") + decodeInputWidth = Param.Unsigned(2, + "Width (in instructions) of input to Decode (and implicitly" + " Decode's own width)") + decodeCycleInput = Param.Bool(True, + "Allow Decode to pack instructions from more than one input cycle" + " to fill its output each cycle") + + executeInputWidth = Param.Unsigned(2, + "Width (in instructions) of input to Execute") + executeCycleInput = Param.Bool(True, + "Allow Execute to use instructions from more than one input cycle" + " each cycle") + executeIssueLimit = Param.Unsigned(2, + "Number of issuable instructions in Execute each cycle") + executeMemoryIssueLimit = Param.Unsigned(1, + "Number of issuable memory instructions in Execute each cycle") + executeCommitLimit = Param.Unsigned(2, + "Number of committable instructions in Execute each cycle") + executeMemoryCommitLimit = Param.Unsigned(1, + "Number of committable memory references in Execute each cycle") + executeInputBufferSize = Param.Unsigned(7, + "Size of input buffer to Execute in cycles-worth of insts.") + executeMemoryWidth = Param.Unsigned(0, + "Width (and snap) in bytes of the data memory interface. (0 mean use" + " the system cacheLineSize)") + executeMaxAccessesInMemory = Param.Unsigned(2, + "Maximum number of concurrent accesses allowed to the memory system" + " from the dcache port") + executeLSQMaxStoreBufferStoresPerCycle = Param.Unsigned(2, + "Maximum number of stores that the store buffer can issue per cycle") + executeLSQRequestsQueueSize = Param.Unsigned(1, + "Size of LSQ requests queue (address translation queue)") + executeLSQTransfersQueueSize = Param.Unsigned(2, + "Size of LSQ transfers queue (memory transaction queue)") + executeLSQStoreBufferSize = Param.Unsigned(5, + "Size of LSQ store buffer") + executeBranchDelay = Param.Cycles(1, + "Delay from Execute deciding to branch and Fetch1 reacting" + " (1 means next cycle)") + + executeFuncUnits = Param.MinorFUPool(MinorDefaultFUPool(), + "FUlines for this processor") + + executeSetTraceTimeOnCommit = Param.Bool(True, + "Set inst. trace times to be commit times") + executeSetTraceTimeOnIssue = Param.Bool(False, + "Set inst. trace times to be issue times") + + executeAllowEarlyMemoryIssue = Param.Bool(True, + "Allow mem refs to be issued to the LSQ before reaching the head of" + " the in flight insts queue") + + enableIdling = Param.Bool(True, + "Enable cycle skipping when the processor is idle\n"); + + branchPred = Param.BranchPredictor(BranchPredictor( + numThreads = Parent.numThreads), "Branch Predictor") + + def addCheckerCpu(self): + print "Checker not yet supported by MinorCPU" + exit(1) diff --git a/src/cpu/minor/SConscript b/src/cpu/minor/SConscript new file mode 100644 index 000000000..2234f9a8d --- /dev/null +++ b/src/cpu/minor/SConscript @@ -0,0 +1,73 @@ +# -*- mode:python -*- + +# Copyright (c) 2013-2014 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) 2006 The Regents of The University of Michigan +# 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. +# +# Authors: Nathan Binkert +# Andrew Bardsley + +Import('*') + +if 'MinorCPU' in env['CPU_MODELS']: + SimObject('MinorCPU.py') + + Source('activity.cc') + Source('cpu.cc') + Source('decode.cc') + Source('dyn_inst.cc') + Source('execute.cc') + Source('fetch1.cc') + Source('fetch2.cc') + Source('func_unit.cc') + Source('lsq.cc') + Source('pipe_data.cc') + Source('pipeline.cc') + Source('scoreboard.cc') + Source('stats.cc') + + DebugFlag('MinorCPU', 'Minor CPU-level events') + DebugFlag('MinorExecute', 'Minor Execute stage') + DebugFlag('MinorInterrupt', 'Minor interrupt handling') + DebugFlag('MinorMem', 'Minor memory accesses') + DebugFlag('MinorScoreboard', 'Minor Execute register scoreboard') + DebugFlag('MinorTrace', 'MinorTrace cycle-by-cycle state trace') + DebugFlag('MinorTiming', 'Extra timing for instructions') + + CompoundFlag('Minor', [ + 'MinorCPU', 'MinorExecute', 'MinorInterrupt', 'MinorMem', + 'MinorScoreboard']) diff --git a/src/cpu/minor/SConsopts b/src/cpu/minor/SConsopts new file mode 100644 index 000000000..68c420779 --- /dev/null +++ b/src/cpu/minor/SConsopts @@ -0,0 +1,45 @@ +# -*- mode:python -*- + +# Copyright (c) 2012-2014 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: Andrew Bardsley + +Import('*') + +CpuModel('MinorCPU', 'minor_cpu_exec.cc', + '#include "cpu/minor/exec_context.hh"', + { 'CPU_exec_context': 'Minor::ExecContext' }, + default=True) diff --git a/src/cpu/minor/activity.cc b/src/cpu/minor/activity.cc new file mode 100644 index 000000000..8e322d3e7 --- /dev/null +++ b/src/cpu/minor/activity.cc @@ -0,0 +1,66 @@ +/* + * Copyright (c) 2013-2014 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: Andrew Bardsley + */ + +#include <sstream> + +#include "cpu/minor/activity.hh" +#include "cpu/minor/trace.hh" + +namespace Minor +{ + +void +MinorActivityRecorder::minorTrace() const +{ + std::ostringstream stages; + unsigned int num_stages = getNumStages(); + + unsigned int stage_index = 0; + while (stage_index < num_stages) { + stages << (getStageActive(stage_index) ? '1' : 'E'); + + stage_index++; + if (stage_index != num_stages) + stages << ','; + } + + MINORTRACE("activity=%d stages=%s\n", getActivityCount(), stages.str()); +} + +} diff --git a/src/cpu/minor/activity.hh b/src/cpu/minor/activity.hh new file mode 100644 index 000000000..e38c476c0 --- /dev/null +++ b/src/cpu/minor/activity.hh @@ -0,0 +1,72 @@ +/* + * Copyright (c) 2013-2014 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: Andrew Bardsley + */ + +/** + * @file + * + * ActivityRecoder from cpu/activity.h wrapped to provide evaluate and + * minorTrace. + */ + +#ifndef __CPU_MINOR_ACTIVITY_HH__ +#define __CPU_MINOR_ACTIVITY_HH__ + +#include "cpu/activity.hh" + +namespace Minor +{ + +/** ActivityRecorder with a Ticked interface */ +class MinorActivityRecorder : public ActivityRecorder +{ + public: + /** Ticked interface */ + void evaluate() { advance(); } + void minorTrace() const; + + public: + MinorActivityRecorder(const std::string &name, int num_stages, + int longest_latency) : + ActivityRecorder(name, num_stages, longest_latency, 0) + { } +}; + +} + +#endif /* __CPU_MINOR_ACTIVITY_HH__ */ diff --git a/src/cpu/minor/buffers.hh b/src/cpu/minor/buffers.hh new file mode 100644 index 000000000..f4ae91a70 --- /dev/null +++ b/src/cpu/minor/buffers.hh @@ -0,0 +1,653 @@ +/* + * Copyright (c) 2013-2014 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: Andrew Bardsley + */ + +/** + * @file + * + * Classes for buffer, queue and FIFO behaviour. + */ + +#ifndef __CPU_MINOR_BUFFERS_HH__ +#define __CPU_MINOR_BUFFERS_HH__ + +#include <iostream> +#include <queue> +#include <sstream> + +#include "cpu/minor/trace.hh" +#include "cpu/activity.hh" +#include "cpu/timebuf.hh" + +namespace Minor +{ + +/** Interface class for data with reporting/tracing facilities. This + * interface doesn't actually have to be used as other classes which need + * this interface uses templating rather than inheritance but it's provided + * here to document the interface needed by those classes. */ +class ReportIF +{ + public: + /** Print the data in a format suitable to be the value in "name=value" + * trace lines */ + virtual void reportData(std::ostream &os) const = 0; + + virtual ~ReportIF() { } +}; + +/** Interface class for data with 'bubble' values. This interface doesn't + * actually have to be used as other classes which need this interface uses + * templating rather than inheritance but it's provided here to document + * the interface needed by those classes. */ +class BubbleIF +{ + public: + virtual bool isBubble() const = 0; +}; + +/** ...ReportTraits are trait classes with the same functionality as + * ReportIF, but with elements explicitly passed into the report... + * functions. */ + +/** Allow a template using ReportTraits to call report... functions of + * ReportIF-bearing elements themselves */ +template <typename ElemType> /* ElemType should implement ReportIF */ +class ReportTraitsAdaptor +{ + public: + static void + reportData(std::ostream &os, const ElemType &elem) + { elem.reportData(os); } +}; + +/** A similar adaptor but for elements held by pointer + * ElemType should implement ReportIF */ +template <typename PtrType> +class ReportTraitsPtrAdaptor +{ + public: + static void + reportData(std::ostream &os, const PtrType &elem) + { elem->reportData(os); } +}; + +/** ... BubbleTraits are trait classes to add BubbleIF interface + * functionality to templates which process elements which don't necessarily + * implement BubbleIF themselves */ + +/** Default behaviour, no bubbles */ +template <typename ElemType> +class NoBubbleTraits +{ + public: + static bool isBubble(const ElemType &) { return false; } + static ElemType bubble() { assert(false); } +}; + +/** Pass on call to the element */ +template <typename ElemType> +class BubbleTraitsAdaptor +{ + public: + static bool isBubble(const ElemType &elem) + { return elem.isBubble(); } + + static ElemType bubble() { return ElemType::bubble(); } +}; + +/** Pass on call to the element where the element is a pointer */ +template <typename PtrType, typename ElemType> +class BubbleTraitsPtrAdaptor +{ + public: + static bool isBubble(const PtrType &elem) + { return elem->isBubble(); } + + static PtrType bubble() { return ElemType::bubble(); } +}; + +/** TimeBuffer with MinorTrace and Named interfaces */ +template <typename ElemType, + typename ReportTraits = ReportTraitsAdaptor<ElemType>, + typename BubbleTraits = BubbleTraitsAdaptor<ElemType> > +class MinorBuffer : public Named, public TimeBuffer<ElemType> +{ + protected: + /** The range of elements that should appear in trace lines */ + int reportLeft, reportRight; + + /** Name to use for the data in a MinorTrace line */ + std::string dataName; + + public: + MinorBuffer(const std::string &name, + const std::string &data_name, + int num_past, int num_future, + int report_left = -1, int report_right = -1) : + Named(name), TimeBuffer<ElemType>(num_past, num_future), + reportLeft(report_left), reportRight(report_right), + dataName(data_name) + { } + + public: + /* Is this buffer full of only bubbles */ + bool + empty() const + { + bool ret = true; + + for (int i = -this->past; i <= this->future; i++) { + if (!BubbleTraits::isBubble((*this)[i])) + ret = false; + } + + return ret; + } + + /** Report buffer states from 'slot' 'from' to 'to'. For example 0,-1 + * will produce two slices with current (just assigned) and last (one + * advance() old) slices with the current (0) one on the left. + * Reverse the numbers to change the order of slices */ + void + minorTrace() const + { + std::ostringstream data; + + int step = (reportLeft > reportRight ? -1 : 1); + int end = reportRight + step; + int i = reportLeft; + + while (i != end) { + const ElemType &datum = (*this)[i]; + + ReportTraits::reportData(data, datum); + i += step; + if (i != end) + data << ','; + } + + MINORTRACE("%s=%s\n", dataName, data.str()); + } +}; + +/** Wraps a MinorBuffer with Input/Output interfaces to ensure that units + * within the model can only see the right end of buffers between them. */ +template <typename Data> +class Latch +{ + public: + typedef MinorBuffer<Data> Buffer; + + protected: + /** Delays, in cycles, writing data into the latch and seeing it on the + * latched wires */ + Cycles delay; + + Buffer buffer; + + public: + /** forward/backwardDelay specify the delay from input to output in each + * direction. These arguments *must* be >= 1 */ + Latch(const std::string &name, + const std::string &data_name, + Cycles delay_ = Cycles(1), + bool report_backwards = false) : + delay(delay_), + buffer(name, data_name, delay_, 0, (report_backwards ? -delay_ : 0), + (report_backwards ? 0 : -delay_)) + { } + + public: + /** Encapsulate wires on either input or output of the latch. + * forward/backward correspond to data direction relative to the + * pipeline. Latched and Immediate specify delay for backward data. + * Immediate data is available to earlier stages *during* the cycle it + * is written */ + class Input + { + public: + typename Buffer::wire inputWire; + + public: + Input(typename Buffer::wire input_wire) : + inputWire(input_wire) + { } + }; + + class Output + { + public: + typename Buffer::wire outputWire; + + public: + Output(typename Buffer::wire output_wire) : + outputWire(output_wire) + { } + }; + + bool empty() const { return buffer.empty(); } + + /** An interface to just the input of the buffer */ + Input input() { return Input(buffer.getWire(0)); } + + /** An interface to just the output of the buffer */ + Output output() { return Output(buffer.getWire(-delay)); } + + void minorTrace() const { buffer.minorTrace(); } + + void evaluate() { buffer.advance(); } +}; + +/** A pipeline simulating class that will stall (not advance when advance() + * is called) if a non-bubble value lies at the far end of the pipeline. + * The user can clear the stall before calling advance to unstall the + * pipeline. */ +template <typename ElemType, + typename ReportTraits, + typename BubbleTraits = BubbleTraitsAdaptor<ElemType> > +class SelfStallingPipeline : public MinorBuffer<ElemType, ReportTraits> +{ + protected: + /** Wire at the input end of the pipeline (for convenience) */ + typename TimeBuffer<ElemType>::wire pushWire; + /** Wire at the output end of the pipeline (for convenience) */ + typename TimeBuffer<ElemType>::wire popWire; + + public: + /** If true, advance will not advance the pipeline */ + bool stalled; + + /** The number of slots with non-bubbles in them */ + unsigned int occupancy; + + public: + SelfStallingPipeline(const std::string &name, + const std::string &data_name, + unsigned depth) : + MinorBuffer<ElemType, ReportTraits> + (name, data_name, depth, 0, -1, -depth), + pushWire(this->getWire(0)), + popWire(this->getWire(-depth)), + stalled(false), + occupancy(0) + { + assert(depth > 0); + + /* Write explicit bubbles to get around the case where the default + * constructor for the element type isn't good enough */ + for (unsigned i = 0; i <= depth; i++) + (*this)[-i] = BubbleTraits::bubble(); + } + + public: + /** Write an element to the back of the pipeline. This doesn't cause + * the pipeline to advance until advance is called. Pushing twice + * without advance-ing will just cause an overwrite of the last push's + * data. */ + void push(ElemType &elem) + { + assert(!alreadyPushed()); + *pushWire = elem; + if (!BubbleTraits::isBubble(elem)) + occupancy++; + } + + /** Peek at the end element of the pipe */ + ElemType &front() { return *popWire; } + + const ElemType &front() const { return *popWire; } + + /** Have we already pushed onto this pipe without advancing */ + bool alreadyPushed() { return !BubbleTraits::isBubble(*pushWire); } + + /** There's data (not a bubble) at the end of the pipe */ + bool isPopable() { return !BubbleTraits::isBubble(front()); } + + /** Try to advance the pipeline. If we're stalled, don't advance. If + * we're not stalled, advance then check to see if we become stalled + * (a non-bubble at the end of the pipe) */ + void + advance() + { + bool data_at_end = isPopable(); + + if (!stalled) { + TimeBuffer<ElemType>::advance(); + /* If there was data at the end of the pipe that has now been + * advanced out of the pipe, we've lost data */ + if (data_at_end) + occupancy--; + /* Is there data at the end of the pipe now? */ + stalled = isPopable(); + /* Insert a bubble into the empty input slot to make sure that + * element is correct in the case where the default constructor + * for ElemType doesn't produce a bubble */ + ElemType bubble = BubbleTraits::bubble(); + *pushWire = bubble; + } + } +}; + +/** Base class for space reservation requestable objects */ +class Reservable +{ + public: + /** Can a slot be reserved? */ + virtual bool canReserve() const = 0; + + /** Reserve a slot in whatever structure this is attached to */ + virtual void reserve() = 0; + + /** Free a reserved slot */ + virtual void freeReservation() = 0; +}; + +/** Wrapper for a queue type to act as a pipeline stage input queue. + * Handles capacity management, bubble value suppression and provides + * reporting. + * + * In an ideal world, ElemType would be derived from ReportIF and BubbleIF, + * but here we use traits and allow the Adaptors ReportTraitsAdaptor and + * BubbleTraitsAdaptor to work on data which *does* directly implement + * those interfaces. */ +template <typename ElemType, + typename ReportTraits = ReportTraitsAdaptor<ElemType>, + typename BubbleTraits = BubbleTraitsAdaptor<ElemType> > +class Queue : public Named, public Reservable +{ + private: + std::deque<ElemType> queue; + + /** Number of slots currently reserved for future (reservation + * respecting) pushes */ + unsigned int numReservedSlots; + + /** Need this here as queues usually don't have a limited capacity */ + unsigned int capacity; + + /** Name to use for the data in MinorTrace */ + std::string dataName; + + public: + Queue(const std::string &name, const std::string &data_name, + unsigned int capacity_) : + Named(name), + numReservedSlots(0), + capacity(capacity_), + dataName(data_name) + { } + + virtual ~Queue() { } + + public: + /** Push an element into the buffer if it isn't a bubble. Bubbles are + * just discarded. It is assummed that any push into a queue with + * reserved space intends to take that space */ + void + push(ElemType &data) + { + if (!BubbleTraits::isBubble(data)) { + freeReservation(); + queue.push_back(data); + + if (queue.size() > capacity) { + warn("%s: No space to push data into queue of capacity" + " %u, pushing anyway\n", name(), capacity); + } + + } + } + + /** Clear all allocated space. Be careful how this is used */ + void clearReservedSpace() { numReservedSlots = 0; } + + /** Clear a single reserved slot */ + void freeReservation() + { + if (numReservedSlots != 0) + numReservedSlots--; + } + + /** Reserve space in the queue for future pushes. Enquiries about space + * in the queue using unreservedRemainingSpace will only tell about + * space which is not full and not reserved. */ + void + reserve() + { + /* Check reservable space */ + if (unreservedRemainingSpace() == 0) + warn("%s: No space is reservable in queue", name()); + + numReservedSlots++; + } + + bool canReserve() const { return unreservedRemainingSpace() != 0; } + + /** Number of slots available in an empty buffer */ + unsigned int totalSpace() const { return capacity; } + + /** Number of slots already occupied in this buffer */ + unsigned int occupiedSpace() const { return queue.size(); } + + /** Number of slots which are reserved. */ + unsigned int reservedSpace() const { return numReservedSlots; } + + /** Number of slots yet to fill in this buffer. This doesn't include + * reservation. */ + unsigned int + remainingSpace() const + { + int ret = capacity - queue.size(); + + return (ret < 0 ? 0 : ret); + } + + /** Like remainingSpace but does not count reserved spaces */ + unsigned int + unreservedRemainingSpace() const + { + int ret = capacity - (queue.size() + numReservedSlots); + + return (ret < 0 ? 0 : ret); + } + + /** Head value. Like std::queue::front */ + ElemType &front() { return queue.front(); } + + const ElemType &front() const { return queue.front(); } + + /** Pop the head item. Like std::queue::pop */ + void pop() { queue.pop_front(); } + + /** Is the queue empty? */ + bool empty() const { return queue.empty(); } + + void + minorTrace() const + { + std::ostringstream data; + /* If we become over-full, totalSpace() can actually be smaller than + * occupiedSpace(). Handle this */ + unsigned int num_total = (occupiedSpace() > totalSpace() ? + occupiedSpace() : totalSpace()); + + unsigned int num_reserved = reservedSpace(); + unsigned int num_occupied = occupiedSpace(); + + int num_printed = 1; + /* Bodge to rotate queue to report elements */ + while (num_printed <= num_occupied) { + ReportTraits::reportData(data, queue[num_printed - 1]); + num_printed++; + + if (num_printed <= num_total) + data << ','; + } + + int num_printed_reserved = 1; + /* Show reserved slots */ + while (num_printed_reserved <= num_reserved && + num_printed <= num_total) + { + data << 'R'; + num_printed_reserved++; + num_printed++; + + if (num_printed <= num_total) + data << ','; + } + + /* And finally pad with empty slots (if there are any) */ + while (num_printed <= num_total) { + num_printed++; + + if (num_printed <= num_total) + data << ','; + } + + MINORTRACE("%s=%s\n", dataName, data.str()); + } +}; + +/** Like a Queue but with a restricted interface and a setTail function + * which, when the queue is empty, just takes a reference to the pushed + * item as the single element. Calling pushTail will push that element + * onto the queue. + * + * The purpose of this class is to allow the faster operation of queues of + * items which usually don't get deeper than one item and for which the copy + * associated with a push is expensive enough to want to avoid + * + * The intended use case is the input buffer for pipeline stages, hence the + * class name */ +template <typename ElemType, + typename ReportTraits = ReportTraitsAdaptor<ElemType>, + typename BubbleTraits = BubbleTraitsAdaptor<ElemType> > +class InputBuffer : public Reservable +{ + protected: + /** Underlying queue */ + mutable Queue<ElemType, ReportTraits, BubbleTraits> queue; + + /** Pointer to the single element (if not NULL) */ + mutable ElemType *elementPtr; + + public: + InputBuffer(const std::string &name, const std::string &data_name, + unsigned int capacity_) : + queue(name, data_name, capacity_), + elementPtr(NULL) + { } + + public: + /** Set the tail of the queue, this is like push but needs + * to be followed by pushTail for the new tail to make its + * way into the queue proper */ + void + setTail(ElemType &new_element) + { + assert(!elementPtr); + if (!BubbleTraits::isBubble(new_element)) { + if (queue.empty()) + elementPtr = &new_element; + else + queue.push(new_element); + } + } + + /** No single element or queue entries */ + bool empty() const { return !elementPtr && queue.empty(); } + + /** Return the element, or the front of the queue */ + const ElemType &front() const + { return (elementPtr ? *elementPtr : queue.front()); } + + ElemType &front() + { return (elementPtr ? *elementPtr : queue.front()); } + + /** Pop either the head, or if none, the head of the queue */ + void + pop() + { + if (elementPtr) { + /* A popped element was expected to be pushed into queue + * and so take a reserved space */ + elementPtr = NULL; + queue.freeReservation(); + } else { + queue.pop(); + } + } + + /** Push the single element (if any) into the queue proper. If the + * element's reference points to a transient object, remember to + * always do this before the end of that object's life */ + void + pushTail() const + { + if (elementPtr) + queue.push(*elementPtr); + elementPtr = NULL; + } + + /** Report elements */ + void + minorTrace() const + { + pushTail(); + queue.minorTrace(); + } + + /** Reservable interface, passed on to queue */ + bool canReserve() const { return queue.canReserve(); } + void reserve() { queue.reserve(); } + void freeReservation() { queue.freeReservation(); } + + /** Like remainingSpace but does not count reserved spaces */ + unsigned int + unreservedRemainingSpace() + { + pushTail(); + return queue.unreservedRemainingSpace(); + } +}; + +} + +#endif /* __CPU_MINOR_BUFFERS_HH__ */ diff --git a/src/cpu/minor/cpu.cc b/src/cpu/minor/cpu.cc new file mode 100644 index 000000000..f7007f6ff --- /dev/null +++ b/src/cpu/minor/cpu.cc @@ -0,0 +1,362 @@ +/* + * Copyright (c) 2012-2014 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: Andrew Bardsley + */ + +#include "arch/utility.hh" +#include "cpu/minor/cpu.hh" +#include "cpu/minor/dyn_inst.hh" +#include "cpu/minor/fetch1.hh" +#include "cpu/minor/pipeline.hh" +#include "debug/Drain.hh" +#include "debug/MinorCPU.hh" +#include "debug/Quiesce.hh" + +MinorCPU::MinorCPU(MinorCPUParams *params) : + BaseCPU(params), + drainManager(NULL) +{ + /* This is only written for one thread at the moment */ + Minor::MinorThread *thread; + + if (FullSystem) { + thread = new Minor::MinorThread(this, 0, params->system, params->itb, + params->dtb, params->isa[0]); + } else { + /* thread_id 0 */ + thread = new Minor::MinorThread(this, 0, params->system, + params->workload[0], params->itb, params->dtb, params->isa[0]); + } + + threads.push_back(thread); + threadActivateEvents.push_back(new ThreadActivateEvent(*this, 0)); + + thread->setStatus(ThreadContext::Halted); + + ThreadContext *tc = thread->getTC(); + + if (params->checker) { + fatal("The Minor model doesn't support checking (yet)\n"); + } + + threadContexts.push_back(tc); + + Minor::MinorDynInst::init(); + + pipeline = new Minor::Pipeline(*this, *params); + activityRecorder = pipeline->getActivityRecorder(); +} + +MinorCPU::~MinorCPU() +{ + delete pipeline; + + for (ThreadID thread_id = 0; thread_id < threads.size(); thread_id++) { + delete threads[thread_id]; + delete threadActivateEvents[thread_id]; + } +} + +void +MinorCPU::init() +{ + BaseCPU::init(); + + if (!params()->switched_out && + system->getMemoryMode() != Enums::timing) + { + fatal("The Minor CPU requires the memory system to be in " + "'timing' mode.\n"); + } + + /* Initialise the ThreadContext's memory proxies */ + for (ThreadID thread_id = 0; thread_id < threads.size(); thread_id++) { + ThreadContext *tc = getContext(thread_id); + + tc->initMemProxies(tc); + } + + /* Initialise CPUs (== threads in the ISA) */ + if (FullSystem && !params()->switched_out) { + for (ThreadID thread_id = 0; thread_id < threads.size(); thread_id++) + { + ThreadContext *tc = getContext(thread_id); + + /* Initialize CPU, including PC */ + TheISA::initCPU(tc, cpuId()); + } + } +} + +/** Stats interface from SimObject (by way of BaseCPU) */ +void +MinorCPU::regStats() +{ + BaseCPU::regStats(); + stats.regStats(name(), *this); + pipeline->regStats(); +} + +void +MinorCPU::serializeThread(std::ostream &os, ThreadID thread_id) +{ + threads[thread_id]->serialize(os); +} + +void +MinorCPU::unserializeThread(Checkpoint *cp, const std::string §ion, + ThreadID thread_id) +{ + if (thread_id != 0) + fatal("Trying to load more than one thread into a MinorCPU\n"); + + threads[thread_id]->unserialize(cp, section); +} + +void +MinorCPU::serialize(std::ostream &os) +{ + pipeline->serialize(os); + BaseCPU::serialize(os); +} + +void +MinorCPU::unserialize(Checkpoint *cp, const std::string §ion) +{ + pipeline->unserialize(cp, section); + BaseCPU::unserialize(cp, section); +} + +Addr +MinorCPU::dbg_vtophys(Addr addr) +{ + /* Note that this gives you the translation for thread 0 */ + panic("No implementation for vtophy\n"); + + return 0; +} + +void +MinorCPU::wakeup() +{ + DPRINTF(Drain, "MinorCPU wakeup\n"); + + for (auto i = threads.begin(); i != threads.end(); i ++) { + if ((*i)->status() == ThreadContext::Suspended) + (*i)->activate(); + } + + DPRINTF(Drain,"Suspended Processor awoke\n"); +} + +void +MinorCPU::startup() +{ + DPRINTF(MinorCPU, "MinorCPU startup\n"); + + BaseCPU::startup(); + + for (auto i = threads.begin(); i != threads.end(); i ++) + (*i)->startup(); +} + +unsigned int +MinorCPU::drain(DrainManager *drain_manager) +{ + DPRINTF(Drain, "MinorCPU drain\n"); + + drainManager = drain_manager; + + /* Need to suspend all threads and wait for Execute to idle. + * Tell Fetch1 not to fetch */ + unsigned int ret = pipeline->drain(drain_manager); + + if (ret == 0) + DPRINTF(Drain, "MinorCPU drained\n"); + else + DPRINTF(Drain, "MinorCPU not finished draining\n"); + + return ret; +} + +void +MinorCPU::signalDrainDone() +{ + DPRINTF(Drain, "MinorCPU drain done\n"); + setDrainState(Drainable::Drained); + drainManager->signalDrainDone(); + drainManager = NULL; +} + +void +MinorCPU::drainResume() +{ + assert(getDrainState() == Drainable::Drained || + getDrainState() == Drainable::Running); + + if (switchedOut()) { + DPRINTF(Drain, "drainResume while switched out. Ignoring\n"); + return; + } + + DPRINTF(Drain, "MinorCPU drainResume\n"); + + if (!system->isTimingMode()) { + fatal("The Minor CPU requires the memory system to be in " + "'timing' mode.\n"); + } + + wakeup(); + pipeline->drainResume(); + + setDrainState(Drainable::Running); +} + +void +MinorCPU::memWriteback() +{ + DPRINTF(Drain, "MinorCPU memWriteback\n"); +} + +void +MinorCPU::switchOut() +{ + DPRINTF(MinorCPU, "MinorCPU switchOut\n"); + + assert(!switchedOut()); + BaseCPU::switchOut(); + + /* Check that the CPU is drained? */ + activityRecorder->reset(); +} + +void +MinorCPU::takeOverFrom(BaseCPU *old_cpu) +{ + DPRINTF(MinorCPU, "MinorCPU takeOverFrom\n"); + + BaseCPU::takeOverFrom(old_cpu); + + /* Don't think I need to do anything here */ +} + +void +MinorCPU::activateContext(ThreadID thread_id, Cycles delay) +{ + DPRINTF(MinorCPU, "ActivateContext thread: %d delay: %d\n", + thread_id, delay); + + if (!threadActivateEvents[thread_id]->scheduled()) { + schedule(threadActivateEvents[thread_id], clockEdge(delay)); + } +} + +void +MinorCPU::ThreadActivateEvent::process() +{ + DPRINTFS(MinorCPU, (&cpu), "Activating thread: %d\n", thread_id); + + /* Do some cycle accounting. lastStopped is reset to stop the + * wakeup call on the pipeline from adding the quiesce period + * to BaseCPU::numCycles */ + cpu.stats.quiesceCycles += cpu.pipeline->cyclesSinceLastStopped(); + cpu.pipeline->resetLastStopped(); + + /* Wake up the thread, wakeup the pipeline tick */ + cpu.threads[thread_id]->activate(); + cpu.wakeupOnEvent(Minor::Pipeline::CPUStageId); + cpu.pipeline->wakeupFetch(); +} + +void +MinorCPU::suspendContext(ThreadID thread_id) +{ + DPRINTF(MinorCPU, "SuspendContext %d\n", thread_id); + + threads[thread_id]->suspend(); +} + +void +MinorCPU::wakeupOnEvent(unsigned int stage_id) +{ + DPRINTF(Quiesce, "Event wakeup from stage %d\n", stage_id); + + /* Mark that some activity has taken place and start the pipeline */ + activityRecorder->activateStage(stage_id); + pipeline->start(); +} + +MinorCPU * +MinorCPUParams::create() +{ + numThreads = 1; + if (!FullSystem && workload.size() != 1) + panic("only one workload allowed"); + return new MinorCPU(this); +} + +MasterPort &MinorCPU::getInstPort() +{ + return pipeline->getInstPort(); +} + +MasterPort &MinorCPU::getDataPort() +{ + return pipeline->getDataPort(); +} + +Counter +MinorCPU::totalInsts() const +{ + Counter ret = 0; + + for (auto i = threads.begin(); i != threads.end(); i ++) + ret += (*i)->numInst; + + return ret; +} + +Counter +MinorCPU::totalOps() const +{ + Counter ret = 0; + + for (auto i = threads.begin(); i != threads.end(); i ++) + ret += (*i)->numOp; + + return ret; +} diff --git a/src/cpu/minor/cpu.hh b/src/cpu/minor/cpu.hh new file mode 100644 index 000000000..80f41b5d2 --- /dev/null +++ b/src/cpu/minor/cpu.hh @@ -0,0 +1,197 @@ +/* + * Copyright (c) 2012-2014 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: Andrew Bardsley + */ + +/** + * @file + * + * Top level definition of the Minor in-order CPU model + */ + +#ifndef __CPU_MINOR_CPU_HH__ +#define __CPU_MINOR_CPU_HH__ + +#include "cpu/minor/activity.hh" +#include "cpu/minor/stats.hh" +#include "cpu/base.hh" +#include "cpu/simple_thread.hh" +#include "params/MinorCPU.hh" + +namespace Minor +{ +/** Forward declared to break the cyclic inclusion dependencies between + * pipeline and cpu */ +class Pipeline; + +/** Minor will use the SimpleThread state for now */ +typedef SimpleThread MinorThread; +}; + +/** + * MinorCPU is an in-order CPU model with four fixed pipeline stages: + * + * Fetch1 - fetches lines from memory + * Fetch2 - decomposes lines into macro-op instructions + * Decode - decomposes macro-ops into micro-ops + * Execute - executes those micro-ops + * + * This pipeline is carried in the MinorCPU::pipeline object. + * The exec_context interface is not carried by MinorCPU but by + * Minor::ExecContext objects + * created by Minor::Execute. + */ +class MinorCPU : public BaseCPU +{ + protected: + /** Event for delayed wakeup of a thread */ + class ThreadActivateEvent : public Event + { + public: + MinorCPU &cpu; + ThreadID thread_id; + + ThreadActivateEvent(MinorCPU &cpu_, ThreadID thread_id_) : + cpu(cpu_), thread_id(thread_id_) + { } + + void process(); + }; + + /** Events to wakeup each thread */ + std::vector<ThreadActivateEvent *> threadActivateEvents; + + /** pipeline is a container for the clockable pipeline stage objects. + * Elements of pipeline call TheISA to implement the model. */ + Minor::Pipeline *pipeline; + + public: + /** Activity recording for pipeline. This belongs to Pipeline but + * stages will access it through the CPU as the MinorCPU object + * actually mediates idling behaviour */ + Minor::MinorActivityRecorder *activityRecorder; + + /** These are thread state-representing objects for this CPU. If + * you need a ThreadContext for *any* reason, use + * threads[threadId]->getTC() */ + std::vector<Minor::MinorThread *> threads; + + public: + /** Provide a non-protected base class for Minor's Ports as derived + * classes are created by Fetch1 and Execute */ + class MinorCPUPort : public MasterPort + { + public: + /** The enclosing cpu */ + MinorCPU &cpu; + + public: + MinorCPUPort(const std::string& name_, MinorCPU &cpu_) + : MasterPort(name_, &cpu_), cpu(cpu_) + { } + + protected: + /** Snooping a coherence request, do nothing. */ + virtual void recvTimingSnoopReq(PacketPtr pkt) { } + }; + + /** The DrainManager passed into drain that needs be signalled when + * draining is complete */ + DrainManager *drainManager; + + protected: + /** Return a reference to the data port. */ + MasterPort &getDataPort(); + + /** Return a reference to the instruction port. */ + MasterPort &getInstPort(); + + public: + MinorCPU(MinorCPUParams *params); + + ~MinorCPU(); + + public: + /** Starting, waking and initialisation */ + void init(); + void startup(); + void wakeup(); + + Addr dbg_vtophys(Addr addr); + + /** Processor-specific statistics */ + Minor::MinorStats stats; + + /** Stats interface from SimObject (by way of BaseCPU) */ + void regStats(); + + /** Simple inst count interface from BaseCPU */ + Counter totalInsts() const; + Counter totalOps() const; + + void serializeThread(std::ostream &os, ThreadID thread_id); + void unserializeThread(Checkpoint *cp, const std::string §ion, + ThreadID thread_id); + + /** Serialize pipeline data */ + void serialize(std::ostream &os); + void unserialize(Checkpoint *cp, const std::string §ion); + + /** Drain interface */ + unsigned int drain(DrainManager *drain_manager); + void drainResume(); + /** Signal from Pipeline that MinorCPU should signal the DrainManager + * that a drain is complete and set its drainState */ + void signalDrainDone(); + void memWriteback(); + + /** Switching interface from BaseCPU */ + void switchOut(); + void takeOverFrom(BaseCPU *old_cpu); + + /** Thread activation interface from BaseCPU. */ + void activateContext(ThreadID thread_id, Cycles delay); + void suspendContext(ThreadID thread_id); + + /** Interface for stages to signal that they have become active after + * a callback or eventq event where the pipeline itself may have + * already been idled. The stage argument should be from the + * enumeration Pipeline::StageId */ + void wakeupOnEvent(unsigned int stage_id); +}; + +#endif /* __CPU_MINOR_CPU_HH__ */ diff --git a/src/cpu/minor/decode.cc b/src/cpu/minor/decode.cc new file mode 100644 index 000000000..e380f0d2d --- /dev/null +++ b/src/cpu/minor/decode.cc @@ -0,0 +1,295 @@ +/* + * Copyright (c) 2013-2014 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: Andrew Bardsley + */ + +#include "cpu/minor/decode.hh" +#include "cpu/minor/pipeline.hh" +#include "debug/Decode.hh" + +namespace Minor +{ + +Decode::Decode(const std::string &name, + MinorCPU &cpu_, + MinorCPUParams ¶ms, + Latch<ForwardInstData>::Output inp_, + Latch<ForwardInstData>::Input out_, + Reservable &next_stage_input_buffer) : + Named(name), + cpu(cpu_), + inp(inp_), + out(out_), + nextStageReserve(next_stage_input_buffer), + outputWidth(params.executeInputWidth), + processMoreThanOneInput(params.decodeCycleInput), + inputBuffer(name + ".inputBuffer", "insts", params.decodeInputBufferSize), + inputIndex(0), + inMacroop(false), + execSeqNum(InstId::firstExecSeqNum) +{ + if (outputWidth < 1) + fatal("%s: executeInputWidth must be >= 1 (%d)\n", name, outputWidth); + + if (params.decodeInputBufferSize < 1) { + fatal("%s: decodeInputBufferSize must be >= 1 (%d)\n", name, + params.decodeInputBufferSize); + } +} + +const ForwardInstData * +Decode::getInput() +{ + /* Get insts from the inputBuffer to work with */ + if (!inputBuffer.empty()) { + const ForwardInstData &head = inputBuffer.front(); + + return (head.isBubble() ? NULL : &(inputBuffer.front())); + } else { + return NULL; + } +} + +void +Decode::popInput() +{ + if (!inputBuffer.empty()) + inputBuffer.pop(); + + inputIndex = 0; + inMacroop = false; +} + +#if TRACING_ON +/** Add the tracing data to an instruction. This originates in + * decode because this is the first place that execSeqNums are known + * (these are used as the 'FetchSeq' in tracing data) */ +static void +dynInstAddTracing(MinorDynInstPtr inst, StaticInstPtr static_inst, + MinorCPU &cpu) +{ + inst->traceData = cpu.getTracer()->getInstRecord(curTick(), + cpu.getContext(inst->id.threadId), + inst->staticInst, inst->pc, static_inst); + + /* Use the execSeqNum as the fetch sequence number as this most closely + * matches the other processor models' idea of fetch sequence */ + if (inst->traceData) + inst->traceData->setFetchSeq(inst->id.execSeqNum); +} +#endif + +void +Decode::evaluate() +{ + inputBuffer.setTail(*inp.outputWire); + ForwardInstData &insts_out = *out.inputWire; + + assert(insts_out.isBubble()); + + blocked = false; + + if (!nextStageReserve.canReserve()) { + blocked = true; + } else { + const ForwardInstData *insts_in = getInput(); + + unsigned int output_index = 0; + + /* Pack instructions into the output while we can. This may involve + * using more than one input line */ + while (insts_in && + inputIndex < insts_in->width() && /* Still more input */ + output_index < outputWidth /* Still more output to fill */) + { + MinorDynInstPtr inst = insts_in->insts[inputIndex]; + + if (inst->isBubble()) { + /* Skip */ + inputIndex++; + inMacroop = false; + } else { + StaticInstPtr static_inst = inst->staticInst; + /* Static inst of a macro-op above the output_inst */ + StaticInstPtr parent_static_inst = NULL; + MinorDynInstPtr output_inst = inst; + + if (inst->isFault()) { + DPRINTF(Decode, "Fault being passed: %d\n", + inst->fault->name()); + + inputIndex++; + inMacroop = false; + } else if (static_inst->isMacroop()) { + /* Generate a new micro-op */ + StaticInstPtr static_micro_inst; + + /* Set up PC for the next micro-op emitted */ + if (!inMacroop) { + microopPC = inst->pc; + inMacroop = true; + } + + /* Get the micro-op static instruction from the + * static_inst. */ + static_micro_inst = + static_inst->fetchMicroop(microopPC.microPC()); + + output_inst = new MinorDynInst(inst->id); + output_inst->pc = microopPC; + output_inst->staticInst = static_micro_inst; + output_inst->fault = NoFault; + + /* Allow a predicted next address only on the last + * microop */ + if (static_micro_inst->isLastMicroop()) { + output_inst->predictedTaken = inst->predictedTaken; + output_inst->predictedTarget = inst->predictedTarget; + } + + DPRINTF(Decode, "Microop decomposition inputIndex:" + " %d output_index: %d lastMicroop: %s microopPC:" + " %d.%d inst: %d\n", + inputIndex, output_index, + (static_micro_inst->isLastMicroop() ? + "true" : "false"), + microopPC.instAddr(), microopPC.microPC(), + *output_inst); + + /* Acknowledge that the static_inst isn't mine, it's my + * parent macro-op's */ + parent_static_inst = static_inst; + + static_micro_inst->advancePC(microopPC); + + /* Step input if this is the last micro-op */ + if (static_micro_inst->isLastMicroop()) { + inputIndex++; + inMacroop = false; + } + } else { + /* Doesn't need decomposing, pass on instruction */ + DPRINTF(Decode, "Passing on inst: %s inputIndex:" + " %d output_index: %d\n", + *output_inst, inputIndex, output_index); + + parent_static_inst = static_inst; + + /* Step input */ + inputIndex++; + inMacroop = false; + } + + /* Set execSeqNum of output_inst */ + output_inst->id.execSeqNum = execSeqNum; + /* Add tracing */ +#if TRACING_ON + dynInstAddTracing(output_inst, parent_static_inst, cpu); +#endif + + /* Step to next sequence number */ + execSeqNum++; + + /* Correctly size the output before writing */ + if(output_index == 0) insts_out.resize(outputWidth); + /* Push into output */ + insts_out.insts[output_index] = output_inst; + output_index++; + } + + /* Have we finished with the input? */ + if (inputIndex == insts_in->width()) { + /* If we have just been producing micro-ops, we *must* have + * got to the end of that for inputIndex to be pushed past + * insts_in->width() */ + assert(!inMacroop); + popInput(); + insts_in = NULL; + + if (processMoreThanOneInput) { + DPRINTF(Decode, "Wrapping\n"); + insts_in = getInput(); + } + } + } + + /* The rest of the output (if any) should already have been packed + * with bubble instructions by insts_out's initialisation + * + * for (; output_index < outputWidth; output_index++) + * assert(insts_out.insts[output_index]->isBubble()); + */ + } + + /* If we generated output, reserve space for the result in the next stage + * and mark the stage as being active this cycle */ + if (!insts_out.isBubble()) { + /* Note activity of following buffer */ + cpu.activityRecorder->activity(); + nextStageReserve.reserve(); + } + + /* If we still have input to process and somewhere to put it, + * mark stage as active */ + if (getInput() && nextStageReserve.canReserve()) + cpu.activityRecorder->activateStage(Pipeline::DecodeStageId); + + /* Make sure the input (if any left) is pushed */ + inputBuffer.pushTail(); +} + +bool +Decode::isDrained() +{ + return inputBuffer.empty() && (*inp.outputWire).isBubble(); +} + +void +Decode::minorTrace() const +{ + std::ostringstream data; + + if (blocked) + data << 'B'; + else + (*out.inputWire).reportData(data); + + MINORTRACE("insts=%s\n", data.str()); + inputBuffer.minorTrace(); +} + +} diff --git a/src/cpu/minor/decode.hh b/src/cpu/minor/decode.hh new file mode 100644 index 000000000..fcc18fd44 --- /dev/null +++ b/src/cpu/minor/decode.hh @@ -0,0 +1,138 @@ +/* + * Copyright (c) 2013-2014 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: Andrew Bardsley + */ + +/** + * @file + * + * Decode collects macro-ops from Fetch2 and splits them into micro-ops + * passed to Execute. + */ + +#ifndef __CPU_MINOR_DECODE_HH__ +#define __CPU_MINOR_DECODE_HH__ + +#include "cpu/minor/buffers.hh" +#include "cpu/minor/cpu.hh" +#include "cpu/minor/dyn_inst.hh" +#include "cpu/minor/pipe_data.hh" + +namespace Minor +{ + +/* Decode takes instructions from Fetch2 and decomposes them into micro-ops + * to feed to Execute. It generates a new sequence number for each + * instruction: execSeqNum. + */ +class Decode : public Named +{ + protected: + /** Pointer back to the containing CPU */ + MinorCPU &cpu; + + /** Input port carrying macro instructions from Fetch2 */ + Latch<ForwardInstData>::Output inp; + /** Output port carrying micro-op decomposed instructions to Execute */ + Latch<ForwardInstData>::Input out; + + /** Interface to reserve space in the next stage */ + Reservable &nextStageReserve; + + /** Width of output of this stage/input of next in instructions */ + unsigned int outputWidth; + + /** If true, more than one input word can be processed each cycle if + * there is room in the output to contain its processed data */ + bool processMoreThanOneInput; + + public: + /* Public for Pipeline to be able to pass it to Fetch2 */ + InputBuffer<ForwardInstData> inputBuffer; + + protected: + /** Data members after this line are cycle-to-cycle state */ + + /** Index into the inputBuffer's head marking the start of unhandled + * instructions */ + unsigned int inputIndex; + + /** True when we're in the process of decomposing a micro-op and + * microopPC will be valid. This is only the case when there isn't + * sufficient space in Executes input buffer to take the whole of a + * decomposed instruction and some of that instructions micro-ops must + * be generated in a later cycle */ + bool inMacroop; + TheISA::PCState microopPC; + + /** Source of execSeqNums to number instructions. */ + InstSeqNum execSeqNum; + + /** Blocked indication for report */ + bool blocked; + + protected: + /** Get a piece of data to work on, or 0 if there is no data. */ + const ForwardInstData *getInput(); + + /** Pop an element off the input buffer, if there are any */ + void popInput(); + + public: + Decode(const std::string &name, + MinorCPU &cpu_, + MinorCPUParams ¶ms, + Latch<ForwardInstData>::Output inp_, + Latch<ForwardInstData>::Input out_, + Reservable &next_stage_input_buffer); + + public: + /** Pass on input/buffer data to the output if you can */ + void evaluate(); + + void minorTrace() const; + + /** Is this stage drained? For Decoed, draining is initiated by + * Execute halting Fetch1 causing Fetch2 to naturally drain + * into Decode and on to Execute which is responsible for + * actually killing instructions */ + bool isDrained(); +}; + +} + +#endif /* __CPU_MINOR_DECODE_HH__ */ diff --git a/src/cpu/minor/dyn_inst.cc b/src/cpu/minor/dyn_inst.cc new file mode 100644 index 000000000..ab08e6b4a --- /dev/null +++ b/src/cpu/minor/dyn_inst.cc @@ -0,0 +1,227 @@ +/* + * Copyright (c) 2013-2014 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: Andrew Bardsley + */ + +#include <iomanip> +#include <sstream> + +#include "arch/isa.hh" +#include "arch/registers.hh" +#include "cpu/minor/dyn_inst.hh" +#include "cpu/minor/trace.hh" +#include "cpu/base.hh" +#include "cpu/reg_class.hh" +#include "debug/MinorExecute.hh" +#include "enums/OpClass.hh" + +namespace Minor +{ + +std::ostream & +operator <<(std::ostream &os, const InstId &id) +{ + os << id.threadId << '/' << id.streamSeqNum << '.' + << id.predictionSeqNum << '/' << id.lineSeqNum; + + /* Not all structures have fetch and exec sequence numbers */ + if (id.fetchSeqNum != 0) { + os << '/' << id.fetchSeqNum; + if (id.execSeqNum != 0) + os << '.' << id.execSeqNum; + } + + return os; +} + +MinorDynInstPtr MinorDynInst::bubbleInst = NULL; + +void +MinorDynInst::init() +{ + if (!bubbleInst) { + bubbleInst = new MinorDynInst(); + assert(bubbleInst->isBubble()); + /* Make bubbleInst immortal */ + bubbleInst->incref(); + } +} + +bool +MinorDynInst::isLastOpInInst() const +{ + assert(staticInst); + return !(staticInst->isMicroop() && !staticInst->isLastMicroop()); +} + +bool +MinorDynInst::isNoCostInst() const +{ + return isInst() && staticInst->opClass() == No_OpClass; +} + +void +MinorDynInst::reportData(std::ostream &os) const +{ + if (isBubble()) + os << "-"; + else if (isFault()) + os << "F;" << id; + else + os << id; +} + +std::ostream & +operator <<(std::ostream &os, const MinorDynInst &inst) +{ + os << inst.id << " pc: 0x" + << std::hex << inst.pc.instAddr() << std::dec << " ("; + + if (inst.isFault()) + os << "fault: \"" << inst.fault->name() << '"'; + else if (inst.staticInst) + os << inst.staticInst->getName(); + else + os << "bubble"; + + os << ')'; + + return os; +} + +/** Print a register in the form r<n>, f<n>, m<n>(<name>), z for integer, + * float, misc and zero registers given an 'architectural register number' */ +static void +printRegName(std::ostream &os, TheISA::RegIndex reg) +{ + RegClass reg_class = regIdxToClass(reg); + + switch (reg_class) + { + case MiscRegClass: + { + TheISA::RegIndex misc_reg = reg - TheISA::Misc_Reg_Base; + + /* This is an ugly test because not all archs. have miscRegName */ +#if THE_ISA == ARM_ISA + os << 'm' << misc_reg << '(' << TheISA::miscRegName[misc_reg] << + ')'; +#else + os << 'n' << misc_reg; +#endif + } + break; + case FloatRegClass: + os << 'f' << static_cast<unsigned int>(reg - TheISA::FP_Reg_Base); + break; + case IntRegClass: + if (reg == TheISA::ZeroReg) { + os << 'z'; + } else { + os << 'r' << static_cast<unsigned int>(reg); + } + break; + case CCRegClass: + os << 'c' << static_cast<unsigned int>(reg - TheISA::CC_Reg_Base); + } +} + +void +MinorDynInst::minorTraceInst(const Named &named_object) const +{ + if (isFault()) { + MINORINST(&named_object, "id=F;%s addr=0x%x fault=\"%s\"\n", + id, pc.instAddr(), fault->name()); + } else { + unsigned int num_src_regs = staticInst->numSrcRegs(); + unsigned int num_dest_regs = staticInst->numDestRegs(); + + std::ostringstream regs_str; + + /* Format lists of src and dest registers for microops and + * 'full' instructions */ + if (!staticInst->isMacroop()) { + regs_str << " srcRegs="; + + unsigned int src_reg = 0; + while (src_reg < num_src_regs) { + printRegName(regs_str, staticInst->srcRegIdx(src_reg)); + + src_reg++; + if (src_reg != num_src_regs) + regs_str << ','; + } + + regs_str << " destRegs="; + + unsigned int dest_reg = 0; + while (dest_reg < num_dest_regs) { + printRegName(regs_str, staticInst->destRegIdx(dest_reg)); + + dest_reg++; + if (dest_reg != num_dest_regs) + regs_str << ','; + } + +#if THE_ISA == ARM_ISA + regs_str << " extMachInst=" << std::hex << std::setw(16) + << std::setfill('0') << staticInst->machInst << std::dec; +#endif + } + + std::ostringstream flags; + staticInst->printFlags(flags, " "); + + MINORINST(&named_object, "id=%s addr=0x%x inst=\"%s\" class=%s" + " flags=\"%s\"%s%s\n", + id, pc.instAddr(), + (staticInst->opClass() == No_OpClass ? + "(invalid)" : staticInst->disassemble(0,NULL)), + Enums::OpClassStrings[staticInst->opClass()], + flags.str(), + regs_str.str(), + (predictedTaken ? " predictedTaken" : "")); + } +} + +MinorDynInst::~MinorDynInst() +{ + if (traceData) + delete traceData; +} + +} diff --git a/src/cpu/minor/dyn_inst.hh b/src/cpu/minor/dyn_inst.hh new file mode 100644 index 000000000..a30d68819 --- /dev/null +++ b/src/cpu/minor/dyn_inst.hh @@ -0,0 +1,281 @@ +/* + * Copyright (c) 2013-2014 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: Andrew Bardsley + */ + +/** + * @file + * + * The dynamic instruction and instruction/line id (sequence numbers) + * definition for Minor. A spirited attempt is made here to not carry too + * much on this structure. + */ + +#ifndef __CPU_MINOR_DYN_INST_HH__ +#define __CPU_MINOR_DYN_INST_HH__ + +#include <iostream> + +#include "base/refcnt.hh" +#include "cpu/minor/buffers.hh" +#include "cpu/inst_seq.hh" +#include "cpu/static_inst.hh" +#include "cpu/timing_expr.hh" +#include "sim/faults.hh" + +namespace Minor +{ + +class MinorDynInst; + +/** MinorDynInsts are currently reference counted. */ +typedef RefCountingPtr<MinorDynInst> MinorDynInstPtr; + +/** Id for lines and instructions. This includes all the relevant sequence + * numbers and thread ids for all stages of execution. */ +class InstId +{ + public: + /** First sequence numbers to use in initialisation of the pipeline and + * to be expected on the first line/instruction issued */ + static const InstSeqNum firstStreamSeqNum = 1; + static const InstSeqNum firstPredictionSeqNum = 1; + static const InstSeqNum firstLineSeqNum = 1; + static const InstSeqNum firstFetchSeqNum = 1; + static const InstSeqNum firstExecSeqNum = 1; + + public: + /** The thread to which this line/instruction belongs */ + ThreadID threadId; + + /** The 'stream' this instruction belongs to. Streams are interrupted + * (and sequence numbers increased) when Execute finds it wants to + * change the stream of instructions due to a branch. */ + InstSeqNum streamSeqNum; + + /** The predicted qualifier to stream, attached by Fetch2 as a + * consequence of branch prediction */ + InstSeqNum predictionSeqNum; + + /** Line sequence number. This is the sequence number of the fetched + * line from which this instruction was fetched */ + InstSeqNum lineSeqNum; + + /** Fetch sequence number. This is 0 for bubbles and an ascending + * sequence for the stream of all fetched instructions */ + InstSeqNum fetchSeqNum; + + /** 'Execute' sequence number. These are assigned after micro-op + * decomposition and form an ascending sequence (starting with 1) for + * post-micro-op decomposed instructions. */ + InstSeqNum execSeqNum; + + public: + /** Very boring default constructor */ + InstId( + ThreadID thread_id = 0, InstSeqNum stream_seq_num = 0, + InstSeqNum prediction_seq_num = 0, InstSeqNum line_seq_num = 0, + InstSeqNum fetch_seq_num = 0, InstSeqNum exec_seq_num = 0) : + threadId(thread_id), streamSeqNum(stream_seq_num), + predictionSeqNum(prediction_seq_num), lineSeqNum(line_seq_num), + fetchSeqNum(fetch_seq_num), execSeqNum(exec_seq_num) + { } + + public: + /* Equal if the thread and last set sequence number matches */ + bool + operator== (const InstId &rhs) + { + /* If any of fetch and exec sequence number are not set + * they need to be 0, so a straight comparison is still + * fine */ + bool ret = (threadId == rhs.threadId && + lineSeqNum == rhs.lineSeqNum && + fetchSeqNum == rhs.fetchSeqNum && + execSeqNum == rhs.execSeqNum); + + /* Stream and prediction *must* match if these are the same id */ + if (ret) { + assert(streamSeqNum == rhs.streamSeqNum && + predictionSeqNum == rhs.predictionSeqNum); + } + + return ret; + } +}; + +/** Print this id in the usual slash-separated format expected by + * MinorTrace */ +std::ostream &operator <<(std::ostream &os, const InstId &id); + +class MinorDynInst; + +/** Print a short reference to this instruction. '-' for a bubble and a + * series of '/' separated sequence numbers for other instructions. The + * sequence numbers will be in the order: stream, prediction, line, fetch, + * exec with exec absent if it is 0. This is used by MinorTrace. */ +std::ostream &operator <<(std::ostream &os, const MinorDynInst &inst); + +/** Dynamic instruction for Minor. + * MinorDynInst implements the BubbleIF interface + * Has two separate notions of sequence number for pre/post-micro-op + * decomposition: fetchSeqNum and execSeqNum */ +class MinorDynInst : public RefCounted +{ + private: + /** A prototypical bubble instruction. You must call MinorDynInst::init + * to initialise this */ + static MinorDynInstPtr bubbleInst; + + public: + StaticInstPtr staticInst; + + InstId id; + + /** Trace information for this instruction's execution */ + Trace::InstRecord *traceData; + + /** The fetch address of this instruction */ + TheISA::PCState pc; + + /** This is actually a fault masquerading as an instruction */ + Fault fault; + + /** Tried to predict the destination of this inst (if a control + * instruction or a sys call) */ + bool triedToPredict; + + /** This instruction was predicted to change control flow and + * the following instructions will have a newer predictionSeqNum */ + bool predictedTaken; + + /** Predicted branch target */ + TheISA::PCState predictedTarget; + + /** Fields only set during execution */ + + /** FU this instruction is issued to */ + unsigned int fuIndex; + + /** This instruction is in the LSQ, not a functional unit */ + bool inLSQ; + + /** The instruction has been sent to the store buffer */ + bool inStoreBuffer; + + /** Can this instruction be executed out of order. In this model, + * this only happens with mem refs that need to be issued early + * to allow other instructions to fill the fetch delay */ + bool canEarlyIssue; + + /** execSeqNum of the latest inst on which this inst depends. + * This can be used as a sanity check for dependency ordering + * where slightly out of order execution is required (notably + * initiateAcc for memory ops) */ + InstSeqNum instToWaitFor; + + /** Extra delay at the end of the pipeline */ + Cycles extraCommitDelay; + TimingExpr *extraCommitDelayExpr; + + /** Once issued, extraCommitDelay becomes minimumCommitCycle + * to account for delay in absolute time */ + Cycles minimumCommitCycle; + + /** Flat register indices so that, when clearing the scoreboard, we + * have the same register indices as when the instruction was marked + * up */ + TheISA::RegIndex flatDestRegIdx[TheISA::MaxInstDestRegs]; + + /** Effective address as set by ExecContext::setEA */ + Addr ea; + + public: + MinorDynInst(InstId id_ = InstId(), Fault fault_ = NoFault) : + staticInst(NULL), id(id_), traceData(NULL), + pc(TheISA::PCState(0)), fault(fault_), + triedToPredict(false), predictedTaken(false), + fuIndex(0), inLSQ(false), inStoreBuffer(false), + canEarlyIssue(false), + instToWaitFor(0), extraCommitDelay(Cycles(0)), + extraCommitDelayExpr(NULL), minimumCommitCycle(Cycles(0)), + ea(0) + { } + + public: + /** The BubbleIF interface. */ + bool isBubble() const { return id.fetchSeqNum == 0; } + + /** There is a single bubble inst */ + static MinorDynInstPtr bubble() { return bubbleInst; } + + /** Is this a fault rather than instruction */ + bool isFault() const { return fault != NoFault; } + + /** Is this a real instruction */ + bool isInst() const { return !isBubble() && !isFault(); } + + /** Is this a real mem ref instruction */ + bool isMemRef() const { return isInst() && staticInst->isMemRef(); } + + /** Is this an instruction that can be executed `for free' and + * needn't spend time in an FU */ + bool isNoCostInst() const; + + /** Assuming this is not a fault, is this instruction either + * a whole instruction or the last microop from a macroop */ + bool isLastOpInInst() const; + + /** Initialise the class */ + static void init(); + + /** Print (possibly verbose) instruction information for + * MinorTrace using the given Named object's name */ + void minorTraceInst(const Named &named_object) const; + + /** ReportIF interface */ + void reportData(std::ostream &os) const; + + ~MinorDynInst(); +}; + +/** Print a summary of the instruction */ +std::ostream &operator <<(std::ostream &os, const MinorDynInst &inst); + +} + +#endif /* __CPU_MINOR_DYN_INST_HH__ */ diff --git a/src/cpu/minor/exec_context.hh b/src/cpu/minor/exec_context.hh new file mode 100644 index 000000000..df909a95c --- /dev/null +++ b/src/cpu/minor/exec_context.hh @@ -0,0 +1,350 @@ +/* + * Copyright (c) 2011-2014 ARM Limited + * Copyright (c) 2013 Advanced Micro Devices, Inc. + * 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) 2002-2005 The Regents of The University of Michigan + * 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. + * + * Authors: Steve Reinhardt + * Dave Greene + * Nathan Binkert + * Andrew Bardsley + */ + +/** + * @file + * + * ExecContext bears the exec_context interface for Minor. + */ + +#ifndef __CPU_MINOR_EXEC_CONTEXT_HH__ +#define __CPU_MINOR_EXEC_CONTEXT_HH__ + +#include "cpu/minor/execute.hh" +#include "cpu/minor/pipeline.hh" +#include "cpu/base.hh" +#include "cpu/simple_thread.hh" +#include "debug/MinorExecute.hh" + +namespace Minor +{ + +/* Forward declaration of Execute */ +class Execute; + +/** ExecContext bears the exec_context interface for Minor. This nicely + * separates that interface from other classes such as Pipeline, MinorCPU + * and DynMinorInst and makes it easier to see what state is accessed by it. + */ +class ExecContext +{ + public: + MinorCPU &cpu; + + /** ThreadState object, provides all the architectural state. */ + SimpleThread &thread; + + /** The execute stage so we can peek at its contents. */ + Execute &execute; + + /** Instruction for the benefit of memory operations and for PC */ + MinorDynInstPtr inst; + + ExecContext ( + MinorCPU &cpu_, + SimpleThread &thread_, Execute &execute_, + MinorDynInstPtr inst_) : + cpu(cpu_), + thread(thread_), + execute(execute_), + inst(inst_) + { + DPRINTF(MinorExecute, "ExecContext setting PC: %s\n", inst->pc); + pcState(inst->pc); + setPredicate(true); + thread.setIntReg(TheISA::ZeroReg, 0); +#if THE_ISA == ALPHA_ISA + thread.setFloatReg(TheISA::ZeroReg, 0.0); +#endif + } + + Fault + readMem(Addr addr, uint8_t *data, unsigned int size, + unsigned int flags) + { + execute.getLSQ().pushRequest(inst, true /* load */, data, + size, addr, flags, NULL); + return NoFault; + } + + Fault + writeMem(uint8_t *data, unsigned int size, Addr addr, + unsigned int flags, uint64_t *res) + { + execute.getLSQ().pushRequest(inst, false /* store */, data, + size, addr, flags, res); + return NoFault; + } + + uint64_t + readIntRegOperand(const StaticInst *si, int idx) + { + return thread.readIntReg(si->srcRegIdx(idx)); + } + + TheISA::FloatReg + readFloatRegOperand(const StaticInst *si, int idx) + { + int reg_idx = si->srcRegIdx(idx) - TheISA::FP_Reg_Base; + return thread.readFloatReg(reg_idx); + } + + TheISA::FloatRegBits + readFloatRegOperandBits(const StaticInst *si, int idx) + { + int reg_idx = si->srcRegIdx(idx) - TheISA::FP_Reg_Base; + return thread.readFloatRegBits(reg_idx); + } + + void + setIntRegOperand(const StaticInst *si, int idx, uint64_t val) + { + thread.setIntReg(si->destRegIdx(idx), val); + } + + void + setFloatRegOperand(const StaticInst *si, int idx, + TheISA::FloatReg val) + { + int reg_idx = si->destRegIdx(idx) - TheISA::FP_Reg_Base; + thread.setFloatReg(reg_idx, val); + } + + void + setFloatRegOperandBits(const StaticInst *si, int idx, + TheISA::FloatRegBits val) + { + int reg_idx = si->destRegIdx(idx) - TheISA::FP_Reg_Base; + thread.setFloatRegBits(reg_idx, val); + } + + bool + readPredicate() + { + return thread.readPredicate(); + } + + void + setPredicate(bool val) + { + thread.setPredicate(val); + } + + TheISA::PCState + pcState() + { + return thread.pcState(); + } + + void + pcState(const TheISA::PCState &val) + { + thread.pcState(val); + } + + TheISA::MiscReg + readMiscRegNoEffect(int misc_reg) + { + return thread.readMiscRegNoEffect(misc_reg); + } + + TheISA::MiscReg + readMiscReg(int misc_reg) + { + return thread.readMiscReg(misc_reg); + } + + void + setMiscReg(int misc_reg, const TheISA::MiscReg &val) + { + thread.setMiscReg(misc_reg, val); + } + + TheISA::MiscReg + readMiscRegOperand(const StaticInst *si, int idx) + { + int reg_idx = si->srcRegIdx(idx) - TheISA::Misc_Reg_Base; + return thread.readMiscReg(reg_idx); + } + + void + setMiscRegOperand(const StaticInst *si, int idx, + const TheISA::MiscReg &val) + { + int reg_idx = si->destRegIdx(idx) - TheISA::Misc_Reg_Base; + return thread.setMiscReg(reg_idx, val); + } + + Fault + hwrei() + { +#if THE_ISA == ALPHA_ISA + return thread.hwrei(); +#else + return NoFault; +#endif + } + + bool + simPalCheck(int palFunc) + { +#if THE_ISA == ALPHA_ISA + return thread.simPalCheck(palFunc); +#else + return false; +#endif + } + + void + syscall(int64_t callnum) + { + if (FullSystem) + panic("Syscall emulation isn't available in FS mode.\n"); + + thread.syscall(callnum); + } + + ThreadContext *tcBase() { return thread.getTC(); } + + /* @todo, should make stCondFailures persistent somewhere */ + unsigned int readStCondFailures() { return 0; } + unsigned int + setStCondFailures(unsigned int st_cond_failures) + { + return 0; + } + + int contextId() { return thread.contextId(); } + /* ISA-specific (or at least currently ISA singleton) functions */ + + /* X86: TLB twiddling */ + void + demapPage(Addr vaddr, uint64_t asn) + { + thread.getITBPtr()->demapPage(vaddr, asn); + thread.getDTBPtr()->demapPage(vaddr, asn); + } + + TheISA::CCReg + readCCRegOperand(const StaticInst *si, int idx) + { + int reg_idx = si->srcRegIdx(idx) - TheISA::CC_Reg_Base; + return thread.readCCReg(reg_idx); + } + + void + setCCRegOperand(const StaticInst *si, int idx, TheISA::CCReg val) + { + int reg_idx = si->destRegIdx(idx) - TheISA::CC_Reg_Base; + thread.setCCReg(reg_idx, val); + } + + void + demapInstPage(Addr vaddr, uint64_t asn) + { + thread.getITBPtr()->demapPage(vaddr, asn); + } + + void + demapDataPage(Addr vaddr, uint64_t asn) + { + thread.getDTBPtr()->demapPage(vaddr, asn); + } + + /* ALPHA/POWER: Effective address storage */ + void setEA(Addr &ea) + { + inst->ea = ea; + } + + BaseCPU *getCpuPtr() { return &cpu; } + + /* POWER: Effective address storage */ + Addr getEA() + { + return inst->ea; + } + + /* MIPS: other thread register reading/writing */ + uint64_t + readRegOtherThread(unsigned idx, ThreadID tid = InvalidThreadID) + { + SimpleThread *other_thread = (tid == InvalidThreadID + ? &thread : cpu.threads[tid]); + + if (idx < TheISA::FP_Reg_Base) { /* Integer */ + return other_thread->readIntReg(idx); + } else if (idx < TheISA::Misc_Reg_Base) { /* Float */ + return other_thread->readFloatRegBits(idx + - TheISA::FP_Reg_Base); + } else { /* Misc */ + return other_thread->readMiscReg(idx + - TheISA::Misc_Reg_Base); + } + } + + void + setRegOtherThread(unsigned idx, const TheISA::MiscReg &val, + ThreadID tid = InvalidThreadID) + { + SimpleThread *other_thread = (tid == InvalidThreadID + ? &thread : cpu.threads[tid]); + + if (idx < TheISA::FP_Reg_Base) { /* Integer */ + return other_thread->setIntReg(idx, val); + } else if (idx < TheISA::Misc_Reg_Base) { /* Float */ + return other_thread->setFloatRegBits(idx + - TheISA::FP_Reg_Base, val); + } else { /* Misc */ + return other_thread->setMiscReg(idx + - TheISA::Misc_Reg_Base, val); + } + } +}; + +} + +#endif /* __CPU_MINOR_EXEC_CONTEXT_HH__ */ diff --git a/src/cpu/minor/execute.cc b/src/cpu/minor/execute.cc new file mode 100644 index 000000000..2a009a154 --- /dev/null +++ b/src/cpu/minor/execute.cc @@ -0,0 +1,1736 @@ +/* + * Copyright (c) 2013-2014 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: Andrew Bardsley + */ + +#include "arch/locked_mem.hh" +#include "arch/registers.hh" +#include "arch/utility.hh" +#include "cpu/minor/cpu.hh" +#include "cpu/minor/exec_context.hh" +#include "cpu/minor/execute.hh" +#include "cpu/minor/fetch1.hh" +#include "cpu/minor/lsq.hh" +#include "cpu/op_class.hh" +#include "debug/Activity.hh" +#include "debug/Branch.hh" +#include "debug/Drain.hh" +#include "debug/MinorExecute.hh" +#include "debug/MinorInterrupt.hh" +#include "debug/MinorMem.hh" +#include "debug/MinorTrace.hh" +#include "debug/PCEvent.hh" + +namespace Minor +{ + +Execute::Execute(const std::string &name_, + MinorCPU &cpu_, + MinorCPUParams ¶ms, + Latch<ForwardInstData>::Output inp_, + Latch<BranchData>::Input out_) : + Named(name_), + inp(inp_), + out(out_), + cpu(cpu_), + issueLimit(params.executeIssueLimit), + memoryIssueLimit(params.executeMemoryIssueLimit), + commitLimit(params.executeCommitLimit), + memoryCommitLimit(params.executeMemoryCommitLimit), + processMoreThanOneInput(params.executeCycleInput), + fuDescriptions(*params.executeFuncUnits), + numFuncUnits(fuDescriptions.funcUnits.size()), + setTraceTimeOnCommit(params.executeSetTraceTimeOnCommit), + setTraceTimeOnIssue(params.executeSetTraceTimeOnIssue), + allowEarlyMemIssue(params.executeAllowEarlyMemoryIssue), + noCostFUIndex(fuDescriptions.funcUnits.size() + 1), + lsq(name_ + ".lsq", name_ + ".dcache_port", + cpu_, *this, + params.executeMaxAccessesInMemory, + params.executeMemoryWidth, + params.executeLSQRequestsQueueSize, + params.executeLSQTransfersQueueSize, + params.executeLSQStoreBufferSize, + params.executeLSQMaxStoreBufferStoresPerCycle), + scoreboard(name_ + ".scoreboard"), + inputBuffer(name_ + ".inputBuffer", "insts", + params.executeInputBufferSize), + inputIndex(0), + lastCommitWasEndOfMacroop(true), + instsBeingCommitted(params.executeCommitLimit), + streamSeqNum(InstId::firstStreamSeqNum), + lastPredictionSeqNum(InstId::firstPredictionSeqNum), + drainState(NotDraining) +{ + if (commitLimit < 1) { + fatal("%s: executeCommitLimit must be >= 1 (%d)\n", name_, + commitLimit); + } + + if (issueLimit < 1) { + fatal("%s: executeCommitLimit must be >= 1 (%d)\n", name_, + issueLimit); + } + + if (memoryIssueLimit < 1) { + fatal("%s: executeMemoryIssueLimit must be >= 1 (%d)\n", name_, + memoryIssueLimit); + } + + if (memoryCommitLimit > commitLimit) { + fatal("%s: executeMemoryCommitLimit (%d) must be <=" + " executeCommitLimit (%d)\n", + name_, memoryCommitLimit, commitLimit); + } + + if (params.executeInputBufferSize < 1) { + fatal("%s: executeInputBufferSize must be >= 1 (%d)\n", name_, + params.executeInputBufferSize); + } + + if (params.executeInputBufferSize < 1) { + fatal("%s: executeInputBufferSize must be >= 1 (%d)\n", name_, + params.executeInputBufferSize); + } + + /* This should be large enough to count all the in-FU instructions + * which need to be accounted for in the inFlightInsts + * queue */ + unsigned int total_slots = 0; + + /* Make FUPipelines for each MinorFU */ + for (unsigned int i = 0; i < numFuncUnits; i++) { + std::ostringstream fu_name; + MinorFU *fu_description = fuDescriptions.funcUnits[i]; + + /* Note the total number of instruction slots (for sizing + * the inFlightInst queue) and the maximum latency of any FU + * (for sizing the activity recorder) */ + total_slots += fu_description->opLat; + + fu_name << name_ << ".fu." << i; + + FUPipeline *fu = new FUPipeline(fu_name.str(), *fu_description, cpu); + + funcUnits.push_back(fu); + } + + /** Check that there is a functional unit for all operation classes */ + for (int op_class = No_OpClass + 1; op_class < Num_OpClass; op_class++) { + bool found_fu = false; + unsigned int fu_index = 0; + + while (fu_index < numFuncUnits && !found_fu) + { + if (funcUnits[fu_index]->provides( + static_cast<OpClass>(op_class))) + { + found_fu = true; + } + fu_index++; + } + + if (!found_fu) { + warn("No functional unit for OpClass %s\n", + Enums::OpClassStrings[op_class]); + } + } + + inFlightInsts = new Queue<QueuedInst, + ReportTraitsAdaptor<QueuedInst> >( + name_ + ".inFlightInsts", "insts", total_slots); + + inFUMemInsts = new Queue<QueuedInst, + ReportTraitsAdaptor<QueuedInst> >( + name_ + ".inFUMemInsts", "insts", total_slots); +} + +const ForwardInstData * +Execute::getInput() +{ + /* Get a line from the inputBuffer to work with */ + if (!inputBuffer.empty()) { + const ForwardInstData &head = inputBuffer.front(); + + return (head.isBubble() ? NULL : &(inputBuffer.front())); + } else { + return NULL; + } +} + +void +Execute::popInput() +{ + if (!inputBuffer.empty()) + inputBuffer.pop(); + + inputIndex = 0; +} + +void +Execute::tryToBranch(MinorDynInstPtr inst, Fault fault, BranchData &branch) +{ + ThreadContext *thread = cpu.getContext(inst->id.threadId); + const TheISA::PCState &pc_before = inst->pc; + TheISA::PCState target = thread->pcState(); + + /* Force a branch for SerializeAfter instructions at the end of micro-op + * sequence when we're not suspended */ + bool force_branch = thread->status() != ThreadContext::Suspended && + !inst->isFault() && + inst->isLastOpInInst() && + (inst->staticInst->isSerializeAfter() || + inst->staticInst->isIprAccess()); + + DPRINTF(Branch, "tryToBranch before: %s after: %s%s\n", + pc_before, target, (force_branch ? " (forcing)" : "")); + + /* Will we change the PC to something other than the next instruction? */ + bool must_branch = pc_before != target || + fault != NoFault || + force_branch; + + /* The reason for the branch data we're about to generate, set below */ + BranchData::Reason reason = BranchData::NoBranch; + + if (fault == NoFault) + { + TheISA::advancePC(target, inst->staticInst); + thread->pcState(target); + + DPRINTF(Branch, "Advancing current PC from: %s to: %s\n", + pc_before, target); + } + + if (inst->predictedTaken && !force_branch) { + /* Predicted to branch */ + if (!must_branch) { + /* No branch was taken, change stream to get us back to the + * intended PC value */ + DPRINTF(Branch, "Predicted a branch from 0x%x to 0x%x but" + " none happened inst: %s\n", + inst->pc.instAddr(), inst->predictedTarget.instAddr(), *inst); + + reason = BranchData::BadlyPredictedBranch; + } else if (inst->predictedTarget == target) { + /* Branch prediction got the right target, kill the branch and + * carry on. + * Note that this information to the branch predictor might get + * overwritten by a "real" branch during this cycle */ + DPRINTF(Branch, "Predicted a branch from 0x%x to 0x%x correctly" + " inst: %s\n", + inst->pc.instAddr(), inst->predictedTarget.instAddr(), *inst); + + reason = BranchData::CorrectlyPredictedBranch; + } else { + /* Branch prediction got the wrong target */ + DPRINTF(Branch, "Predicted a branch from 0x%x to 0x%x" + " but got the wrong target (actual: 0x%x) inst: %s\n", + inst->pc.instAddr(), inst->predictedTarget.instAddr(), + target.instAddr() *inst); + + reason = BranchData::BadlyPredictedBranchTarget; + } + } else if (must_branch) { + /* Unpredicted branch */ + DPRINTF(Branch, "Unpredicted branch from 0x%x to 0x%x inst: %s\n", + inst->pc.instAddr(), target.instAddr(), *inst); + + reason = BranchData::UnpredictedBranch; + } else { + /* No branch at all */ + reason = BranchData::NoBranch; + } + + updateBranchData(reason, inst, target, branch); +} + +void +Execute::updateBranchData( + BranchData::Reason reason, + MinorDynInstPtr inst, const TheISA::PCState &target, + BranchData &branch) +{ + if (reason != BranchData::NoBranch) { + /* Bump up the stream sequence number on a real branch*/ + if (BranchData::isStreamChange(reason)) + streamSeqNum++; + + /* Branches (even mis-predictions) don't change the predictionSeqNum, + * just the streamSeqNum */ + branch = BranchData(reason, streamSeqNum, + /* Maintaining predictionSeqNum if there's no inst is just a + * courtesy and looks better on minorview */ + (inst->isBubble() ? lastPredictionSeqNum + : inst->id.predictionSeqNum), + target, inst); + + DPRINTF(Branch, "Branch data signalled: %s\n", branch); + } +} + +void +Execute::handleMemResponse(MinorDynInstPtr inst, + LSQ::LSQRequestPtr response, BranchData &branch, Fault &fault) +{ + ThreadID thread_id = inst->id.threadId; + ThreadContext *thread = cpu.getContext(thread_id); + + ExecContext context(cpu, *cpu.threads[thread_id], *this, inst); + + PacketPtr packet = response->packet; + + bool is_load = inst->staticInst->isLoad(); + bool is_store = inst->staticInst->isStore(); + bool is_prefetch = inst->staticInst->isDataPrefetch(); + + /* If true, the trace's predicate value will be taken from the exec + * context predicate, otherwise, it will be set to false */ + bool use_context_predicate = true; + + if (response->fault != NoFault) { + /* Invoke memory faults. */ + DPRINTF(MinorMem, "Completing fault from DTLB access: %s\n", + response->fault->name()); + + if (inst->staticInst->isPrefetch()) { + DPRINTF(MinorMem, "Not taking fault on prefetch: %s\n", + response->fault->name()); + + /* Don't assign to fault */ + } else { + /* Take the fault raised during the TLB/memory access */ + fault = response->fault; + + fault->invoke(thread, inst->staticInst); + } + } else if (!packet) { + DPRINTF(MinorMem, "Completing failed request inst: %s\n", + *inst); + use_context_predicate = false; + } else if (packet->isError()) { + DPRINTF(MinorMem, "Trying to commit error response: %s\n", + *inst); + + fatal("Received error response packet for inst: %s\n", *inst); + } else if (is_store || is_load || is_prefetch) { + assert(packet); + + DPRINTF(MinorMem, "Memory response inst: %s addr: 0x%x size: %d\n", + *inst, packet->getAddr(), packet->getSize()); + + if (is_load && packet->getSize() > 0) { + DPRINTF(MinorMem, "Memory data[0]: 0x%x\n", + static_cast<unsigned int>(packet->getPtr<uint8_t>()[0])); + } + + /* Complete the memory access instruction */ + fault = inst->staticInst->completeAcc(packet, &context, + inst->traceData); + + if (fault != NoFault) { + /* Invoke fault created by instruction completion */ + DPRINTF(MinorMem, "Fault in memory completeAcc: %s\n", + fault->name()); + fault->invoke(thread, inst->staticInst); + } else { + /* Stores need to be pushed into the store buffer to finish + * them off */ + if (response->needsToBeSentToStoreBuffer()) + lsq.sendStoreToStoreBuffer(response); + } + } else { + fatal("There should only ever be reads, " + "writes or faults at this point\n"); + } + + lsq.popResponse(response); + + if (inst->traceData) { + inst->traceData->setPredicate((use_context_predicate ? + context.readPredicate() : false)); + } + + doInstCommitAccounting(inst); + + /* Generate output to account for branches */ + tryToBranch(inst, fault, branch); +} + +bool +Execute::isInterrupted(ThreadID thread_id) const +{ + return cpu.checkInterrupts(cpu.getContext(thread_id)); +} + +bool +Execute::takeInterrupt(ThreadID thread_id, BranchData &branch) +{ + DPRINTF(MinorInterrupt, "Considering interrupt status from PC: %s\n", + cpu.getContext(thread_id)->pcState()); + + Fault interrupt = cpu.getInterruptController()->getInterrupt + (cpu.getContext(thread_id)); + + if (interrupt != NoFault) { + /* The interrupt *must* set pcState */ + cpu.getInterruptController()->updateIntrInfo + (cpu.getContext(thread_id)); + interrupt->invoke(cpu.getContext(thread_id)); + + assert(!lsq.accessesInFlight()); + + DPRINTF(MinorInterrupt, "Invoking interrupt: %s to PC: %s\n", + interrupt->name(), cpu.getContext(thread_id)->pcState()); + + /* Assume that an interrupt *must* cause a branch. Assert this? */ + + updateBranchData(BranchData::Interrupt, MinorDynInst::bubble(), + cpu.getContext(thread_id)->pcState(), branch); + } + + return interrupt != NoFault; +} + +bool +Execute::executeMemRefInst(MinorDynInstPtr inst, BranchData &branch, + bool &passed_predicate, Fault &fault) +{ + bool issued = false; + + /* Set to true if the mem op. is issued and sent to the mem system */ + passed_predicate = false; + + if (!lsq.canRequest()) { + /* Not acting on instruction yet as the memory + * queues are full */ + issued = false; + } else { + ThreadContext *thread = cpu.getContext(inst->id.threadId); + TheISA::PCState old_pc = thread->pcState(); + + ExecContext context(cpu, *cpu.threads[inst->id.threadId], + *this, inst); + + DPRINTF(MinorExecute, "Initiating memRef inst: %s\n", *inst); + + Fault init_fault = inst->staticInst->initiateAcc(&context, + inst->traceData); + + if (init_fault != NoFault) { + DPRINTF(MinorExecute, "Fault on memory inst: %s" + " initiateAcc: %s\n", *inst, init_fault->name()); + fault = init_fault; + } else { + /* Only set this if the instruction passed its + * predicate */ + passed_predicate = context.readPredicate(); + + /* Set predicate in tracing */ + if (inst->traceData) + inst->traceData->setPredicate(passed_predicate); + + /* If the instruction didn't pass its predicate (and so will not + * progress from here) Try to branch to correct and branch + * mis-prediction. */ + if (!passed_predicate) { + /* Leave it up to commit to handle the fault */ + lsq.pushFailedRequest(inst); + } + } + + /* Restore thread PC */ + thread->pcState(old_pc); + issued = true; + } + + return issued; +} + +/** Increment a cyclic buffer index for indices [0, cycle_size-1] */ +inline unsigned int +cyclicIndexInc(unsigned int index, unsigned int cycle_size) +{ + unsigned int ret = index + 1; + + if (ret == cycle_size) + ret = 0; + + return ret; +} + +/** Decrement a cyclic buffer index for indices [0, cycle_size-1] */ +inline unsigned int +cyclicIndexDec(unsigned int index, unsigned int cycle_size) +{ + int ret = index - 1; + + if (ret < 0) + ret = cycle_size - 1; + + return ret; +} + +unsigned int +Execute::issue(bool only_issue_microops) +{ + const ForwardInstData *insts_in = getInput(); + + /* Early termination if we have no instructions */ + if (!insts_in) + return 0; + + /* Start from the first FU */ + unsigned int fu_index = 0; + + /* Remains true while instructions are still being issued. If any + * instruction fails to issue, this is set to false and we exit issue. + * This strictly enforces in-order issue. For other issue behaviours, + * a more complicated test in the outer while loop below is needed. */ + bool issued = true; + + /* Number of insts issues this cycle to check for issueLimit */ + unsigned num_insts_issued = 0; + + /* Number of memory ops issues this cycle to check for memoryIssueLimit */ + unsigned num_mem_insts_issued = 0; + + /* Number of instructions discarded this cycle in order to enforce a + * discardLimit. @todo, add that parameter? */ + unsigned num_insts_discarded = 0; + + do { + MinorDynInstPtr inst = insts_in->insts[inputIndex]; + ThreadID thread_id = inst->id.threadId; + Fault fault = inst->fault; + bool discarded = false; + bool issued_mem_ref = false; + + if (inst->isBubble()) { + /* Skip */ + issued = true; + } else if (cpu.getContext(thread_id)->status() == + ThreadContext::Suspended) + { + DPRINTF(MinorExecute, "Not issuing inst: %s from suspended" + " thread\n", *inst); + + issued = false; + } else if (inst->id.streamSeqNum != streamSeqNum) { + DPRINTF(MinorExecute, "Discarding inst: %s as its stream" + " state was unexpected, expected: %d\n", + *inst, streamSeqNum); + issued = true; + discarded = true; + } else if (fault == NoFault && only_issue_microops && + /* Is this anything other than a non-first microop */ + (!inst->staticInst->isMicroop() || + !inst->staticInst->isFirstMicroop())) + { + DPRINTF(MinorExecute, "Not issuing new non-microop inst: %s\n", + *inst); + + issued = false; + } else { + /* Try and issue an instruction into an FU, assume we didn't and + * fix that in the loop */ + issued = false; + + /* Try FU from 0 each instruction */ + fu_index = 0; + + /* Try and issue a single instruction stepping through the + * available FUs */ + do { + FUPipeline *fu = funcUnits[fu_index]; + + DPRINTF(MinorExecute, "Trying to issue inst: %s to FU: %d\n", + *inst, fu_index); + + /* Does the examined fu have the OpClass-related capability + * needed to execute this instruction? Faults can always + * issue to any FU but probably should just 'live' in the + * inFlightInsts queue rather than having an FU. */ + bool fu_is_capable = (!inst->isFault() ? + fu->provides(inst->staticInst->opClass()) : true); + + if (inst->isNoCostInst()) { + /* Issue free insts. to a fake numbered FU */ + fu_index = noCostFUIndex; + + /* And start the countdown on activity to allow + * this instruction to get to the end of its FU */ + cpu.activityRecorder->activity(); + + /* Mark the destinations for this instruction as + * busy */ + scoreboard.markupInstDests(inst, cpu.curCycle() + + Cycles(0), cpu.getContext(thread_id), false); + + inst->fuIndex = noCostFUIndex; + inst->extraCommitDelay = Cycles(0); + inst->extraCommitDelayExpr = NULL; + + /* Push the instruction onto the inFlight queue so + * it can be committed in order */ + QueuedInst fu_inst(inst); + inFlightInsts->push(fu_inst); + + issued = true; + + } else if (!fu_is_capable || fu->alreadyPushed()) { + /* Skip */ + if (!fu_is_capable) { + DPRINTF(MinorExecute, "Can't issue as FU: %d isn't" + " capable\n", fu_index); + } else { + DPRINTF(MinorExecute, "Can't issue as FU: %d is" + " already busy\n", fu_index); + } + } else if (fu->stalled) { + DPRINTF(MinorExecute, "Can't issue inst: %s into FU: %d," + " it's stalled\n", + *inst, fu_index); + } else if (!fu->canInsert()) { + DPRINTF(MinorExecute, "Can't issue inst: %s to busy FU" + " for another: %d cycles\n", + *inst, fu->cyclesBeforeInsert()); + } else { + MinorFUTiming *timing = (!inst->isFault() ? + fu->findTiming(inst->staticInst) : NULL); + + const std::vector<Cycles> *src_latencies = + (timing ? &(timing->srcRegsRelativeLats) + : NULL); + + const std::vector<bool> *cant_forward_from_fu_indices = + &(fu->cantForwardFromFUIndices); + + if (timing && timing->suppress) { + DPRINTF(MinorExecute, "Can't issue inst: %s as extra" + " decoding is suppressing it\n", + *inst); + } else if (!scoreboard.canInstIssue(inst, src_latencies, + cant_forward_from_fu_indices, + cpu.curCycle(), cpu.getContext(thread_id))) + { + DPRINTF(MinorExecute, "Can't issue inst: %s yet\n", + *inst); + } else { + /* Can insert the instruction into this FU */ + DPRINTF(MinorExecute, "Issuing inst: %s" + " into FU %d\n", *inst, + fu_index); + + Cycles extra_dest_retire_lat = Cycles(0); + TimingExpr *extra_dest_retire_lat_expr = NULL; + Cycles extra_assumed_lat = Cycles(0); + + /* Add the extraCommitDelay and extraAssumeLat to + * the FU pipeline timings */ + if (timing) { + extra_dest_retire_lat = + timing->extraCommitLat; + extra_dest_retire_lat_expr = + timing->extraCommitLatExpr; + extra_assumed_lat = + timing->extraAssumedLat; + } + + bool issued_mem_ref = inst->isMemRef(); + + QueuedInst fu_inst(inst); + + /* Decorate the inst with FU details */ + inst->fuIndex = fu_index; + inst->extraCommitDelay = extra_dest_retire_lat; + inst->extraCommitDelayExpr = + extra_dest_retire_lat_expr; + + if (issued_mem_ref) { + /* Remember which instruction this memory op + * depends on so that initiateAcc can be called + * early */ + if (allowEarlyMemIssue) { + inst->instToWaitFor = + scoreboard.execSeqNumToWaitFor(inst, + cpu.getContext(thread_id)); + + if (lsq.getLastMemBarrier() > + inst->instToWaitFor) + { + DPRINTF(MinorExecute, "A barrier will" + " cause a delay in mem ref issue of" + " inst: %s until after inst" + " %d(exec)\n", *inst, + lsq.getLastMemBarrier()); + + inst->instToWaitFor = + lsq.getLastMemBarrier(); + } else { + DPRINTF(MinorExecute, "Memory ref inst:" + " %s must wait for inst %d(exec)" + " before issuing\n", + *inst, inst->instToWaitFor); + } + + inst->canEarlyIssue = true; + } + /* Also queue this instruction in the memory ref + * queue to ensure in-order issue to the LSQ */ + DPRINTF(MinorExecute, "Pushing mem inst: %s\n", + *inst); + inFUMemInsts->push(fu_inst); + } + + /* Issue to FU */ + fu->push(fu_inst); + /* And start the countdown on activity to allow + * this instruction to get to the end of its FU */ + cpu.activityRecorder->activity(); + + /* Mark the destinations for this instruction as + * busy */ + scoreboard.markupInstDests(inst, cpu.curCycle() + + fu->description.opLat + + extra_dest_retire_lat + + extra_assumed_lat, + cpu.getContext(thread_id), + issued_mem_ref && extra_assumed_lat == Cycles(0)); + + /* Push the instruction onto the inFlight queue so + * it can be committed in order */ + inFlightInsts->push(fu_inst); + + issued = true; + } + } + + fu_index++; + } while (fu_index != numFuncUnits && !issued); + + if (!issued) + DPRINTF(MinorExecute, "Didn't issue inst: %s\n", *inst); + } + + if (issued) { + /* Generate MinorTrace's MinorInst lines. Do this at commit + * to allow better instruction annotation? */ + if (DTRACE(MinorTrace) && !inst->isBubble()) + inst->minorTraceInst(*this); + + /* Mark up barriers in the LSQ */ + if (!discarded && inst->isInst() && + inst->staticInst->isMemBarrier()) + { + DPRINTF(MinorMem, "Issuing memory barrier inst: %s\n", *inst); + lsq.issuedMemBarrierInst(inst); + } + + if (inst->traceData && setTraceTimeOnIssue) { + inst->traceData->setWhen(curTick()); + } + + if (issued_mem_ref) + num_mem_insts_issued++; + + if (discarded) { + num_insts_discarded++; + } else { + num_insts_issued++; + + if (num_insts_issued == issueLimit) + DPRINTF(MinorExecute, "Reached inst issue limit\n"); + } + + inputIndex++; + DPRINTF(MinorExecute, "Stepping to next inst inputIndex: %d\n", + inputIndex); + } + + /* Got to the end of a line */ + if (inputIndex == insts_in->width()) { + popInput(); + /* Set insts_in to null to force us to leave the surrounding + * loop */ + insts_in = NULL; + + if (processMoreThanOneInput) { + DPRINTF(MinorExecute, "Wrapping\n"); + insts_in = getInput(); + } + } + } while (insts_in && inputIndex < insts_in->width() && + /* We still have instructions */ + fu_index != numFuncUnits && /* Not visited all FUs */ + issued && /* We've not yet failed to issue an instruction */ + num_insts_issued != issueLimit && /* Still allowed to issue */ + num_mem_insts_issued != memoryIssueLimit); + + return num_insts_issued; +} + +bool +Execute::tryPCEvents() +{ + ThreadContext *thread = cpu.getContext(0); + unsigned int num_pc_event_checks = 0; + + /* Handle PC events on instructions */ + Addr oldPC; + do { + oldPC = thread->instAddr(); + cpu.system->pcEventQueue.service(thread); + num_pc_event_checks++; + } while (oldPC != thread->instAddr()); + + if (num_pc_event_checks > 1) { + DPRINTF(PCEvent, "Acting on PC Event to PC: %s\n", + thread->pcState()); + } + + return num_pc_event_checks > 1; +} + +void +Execute::doInstCommitAccounting(MinorDynInstPtr inst) +{ + assert(!inst->isFault()); + + MinorThread *thread = cpu.threads[inst->id.threadId]; + + /* Increment the many and various inst and op counts in the + * thread and system */ + if (!inst->staticInst->isMicroop() || inst->staticInst->isLastMicroop()) + { + thread->numInst++; + thread->numInsts++; + cpu.stats.numInsts++; + } + thread->numOp++; + thread->numOps++; + cpu.stats.numOps++; + cpu.system->totalNumInsts++; + + /* Act on events related to instruction counts */ + cpu.comInstEventQueue[inst->id.threadId]->serviceEvents(thread->numInst); + cpu.system->instEventQueue.serviceEvents(cpu.system->totalNumInsts); + + /* Set the CP SeqNum to the numOps commit number */ + if (inst->traceData) + inst->traceData->setCPSeq(thread->numOp); +} + +bool +Execute::commitInst(MinorDynInstPtr inst, bool early_memory_issue, + BranchData &branch, Fault &fault, bool &committed, + bool &completed_mem_issue) +{ + ThreadID thread_id = inst->id.threadId; + ThreadContext *thread = cpu.getContext(thread_id); + + bool completed_inst = true; + fault = NoFault; + + /* Is the thread for this instruction suspended? In that case, just + * stall as long as there are no pending interrupts */ + if (thread->status() == ThreadContext::Suspended && + !isInterrupted(thread_id)) + { + DPRINTF(MinorExecute, "Not committing inst from suspended thread" + " inst: %s\n", *inst); + completed_inst = false; + } else if (inst->isFault()) { + ExecContext context(cpu, *cpu.threads[thread_id], *this, inst); + + DPRINTF(MinorExecute, "Fault inst reached Execute: %s\n", + inst->fault->name()); + + fault = inst->fault; + inst->fault->invoke(thread, NULL); + + tryToBranch(inst, fault, branch); + } else if (inst->staticInst->isMemRef()) { + /* Memory accesses are executed in two parts: + * executeMemRefInst -- calculates the EA and issues the access + * to memory. This is done here. + * handleMemResponse -- handles the response packet, done by + * Execute::commit + * + * While the memory access is in its FU, the EA is being + * calculated. At the end of the FU, when it is ready to + * 'commit' (in this function), the access is presented to the + * memory queues. When a response comes back from memory, + * Execute::commit will commit it. + */ + bool predicate_passed = false; + bool completed_mem_inst = executeMemRefInst(inst, branch, + predicate_passed, fault); + + if (completed_mem_inst && fault != NoFault) { + if (early_memory_issue) { + DPRINTF(MinorExecute, "Fault in early executing inst: %s\n", + fault->name()); + /* Don't execute the fault, just stall the instruction + * until it gets to the head of inFlightInsts */ + inst->canEarlyIssue = false; + /* Not completed as we'll come here again to pick up + * the fault when we get to the end of the FU */ + completed_inst = false; + } else { + DPRINTF(MinorExecute, "Fault in execute: %s\n", + fault->name()); + fault->invoke(thread, NULL); + + tryToBranch(inst, fault, branch); + completed_inst = true; + } + } else { + completed_inst = completed_mem_inst; + } + completed_mem_issue = completed_inst; + } else if (inst->isInst() && inst->staticInst->isMemBarrier() && + !lsq.canPushIntoStoreBuffer()) + { + DPRINTF(MinorExecute, "Can't commit data barrier inst: %s yet as" + " there isn't space in the store buffer\n", *inst); + + completed_inst = false; + } else { + ExecContext context(cpu, *cpu.threads[thread_id], *this, inst); + + DPRINTF(MinorExecute, "Committing inst: %s\n", *inst); + + fault = inst->staticInst->execute(&context, + inst->traceData); + + /* Set the predicate for tracing and dump */ + if (inst->traceData) + inst->traceData->setPredicate(context.readPredicate()); + + committed = true; + + if (fault != NoFault) { + DPRINTF(MinorExecute, "Fault in execute of inst: %s fault: %s\n", + *inst, fault->name()); + fault->invoke(thread, inst->staticInst); + } + + doInstCommitAccounting(inst); + tryToBranch(inst, fault, branch); + } + + if (completed_inst) { + /* Keep a copy of this instruction's predictionSeqNum just in case + * we need to issue a branch without an instruction (such as an + * interrupt) */ + lastPredictionSeqNum = inst->id.predictionSeqNum; + + /* Check to see if this instruction suspended the current thread. */ + if (!inst->isFault() && + thread->status() == ThreadContext::Suspended && + branch.isBubble() && /* It didn't branch too */ + !isInterrupted(thread_id)) /* Don't suspend if we have + interrupts */ + { + TheISA::PCState resume_pc = cpu.getContext(0)->pcState(); + + assert(resume_pc.microPC() == 0); + + DPRINTF(MinorInterrupt, "Suspending thread: %d from Execute" + " inst: %s\n", inst->id.threadId, *inst); + + cpu.stats.numFetchSuspends++; + + updateBranchData(BranchData::SuspendThread, inst, resume_pc, + branch); + } + } + + return completed_inst; +} + +void +Execute::commit(bool only_commit_microops, bool discard, BranchData &branch) +{ + Fault fault = NoFault; + Cycles now = cpu.curCycle(); + + /** + * Try and execute as many instructions from the end of FU pipelines as + * possible. This *doesn't* include actually advancing the pipelines. + * + * We do this by looping on the front of the inFlightInsts queue for as + * long as we can find the desired instruction at the end of the + * functional unit it was issued to without seeing a branch or a fault. + * In this function, these terms are used: + * complete -- The instruction has finished its passage through + * its functional unit and its fate has been decided + * (committed, discarded, issued to the memory system) + * commit -- The instruction is complete(d), not discarded and has + * its effects applied to the CPU state + * discard(ed) -- The instruction is complete but not committed + * as its streamSeqNum disagrees with the current + * Execute::streamSeqNum + * + * Commits are also possible from two other places: + * + * 1) Responses returning from the LSQ + * 2) Mem ops issued to the LSQ ('committed' from the FUs) earlier + * than their position in the inFlightInsts queue, but after all + * their dependencies are resolved. + */ + + /* Has an instruction been completed? Once this becomes false, we stop + * trying to complete instructions. */ + bool completed_inst = true; + + /* Number of insts committed this cycle to check against commitLimit */ + unsigned int num_insts_committed = 0; + + /* Number of memory access instructions committed to check against + * memCommitLimit */ + unsigned int num_mem_refs_committed = 0; + + if (only_commit_microops && !inFlightInsts->empty()) { + DPRINTF(MinorInterrupt, "Only commit microops %s %d\n", + *(inFlightInsts->front().inst), + lastCommitWasEndOfMacroop); + } + + while (!inFlightInsts->empty() && /* Some more instructions to process */ + !branch.isStreamChange() && /* No real branch */ + fault == NoFault && /* No faults */ + completed_inst && /* Still finding instructions to execute */ + num_insts_committed != commitLimit /* Not reached commit limit */ + ) + { + if (only_commit_microops) { + DPRINTF(MinorInterrupt, "Committing tail of insts before" + " interrupt: %s\n", + *(inFlightInsts->front().inst)); + } + + QueuedInst *head_inflight_inst = &(inFlightInsts->front()); + + InstSeqNum head_exec_seq_num = + head_inflight_inst->inst->id.execSeqNum; + + /* The instruction we actually process if completed_inst + * remains true to the end of the loop body. + * Start by considering the the head of the in flight insts queue */ + MinorDynInstPtr inst = head_inflight_inst->inst; + + bool committed_inst = false; + bool discard_inst = false; + bool completed_mem_ref = false; + bool issued_mem_ref = false; + bool early_memory_issue = false; + + /* Must set this again to go around the loop */ + completed_inst = false; + + /* If we're just completing a macroop before an interrupt or drain, + * can we stil commit another microop (rather than a memory response) + * without crosing into the next full instruction? */ + bool can_commit_insts = !inFlightInsts->empty() && + !(only_commit_microops && lastCommitWasEndOfMacroop); + + /* Can we find a mem response for this inst */ + LSQ::LSQRequestPtr mem_response = + (inst->inLSQ ? lsq.findResponse(inst) : NULL); + + DPRINTF(MinorExecute, "Trying to commit canCommitInsts: %d\n", + can_commit_insts); + + /* Test for PC events after every instruction */ + if (isInbetweenInsts() && tryPCEvents()) { + ThreadContext *thread = cpu.getContext(0); + + /* Branch as there was a change in PC */ + updateBranchData(BranchData::UnpredictedBranch, + MinorDynInst::bubble(), thread->pcState(), branch); + } else if (mem_response && + num_mem_refs_committed < memoryCommitLimit) + { + /* Try to commit from the memory responses next */ + discard_inst = inst->id.streamSeqNum != streamSeqNum || + discard; + + DPRINTF(MinorExecute, "Trying to commit mem response: %s\n", + *inst); + + /* Complete or discard the response */ + if (discard_inst) { + DPRINTF(MinorExecute, "Discarding mem inst: %s as its" + " stream state was unexpected, expected: %d\n", + *inst, streamSeqNum); + + lsq.popResponse(mem_response); + } else { + handleMemResponse(inst, mem_response, branch, fault); + committed_inst = true; + } + + completed_mem_ref = true; + completed_inst = true; + } else if (can_commit_insts) { + /* If true, this instruction will, subject to timing tweaks, + * be considered for completion. try_to_commit flattens + * the `if' tree a bit and allows other tests for inst + * commit to be inserted here. */ + bool try_to_commit = false; + + /* Try and issue memory ops early if they: + * - Can push a request into the LSQ + * - Have reached the end of their FUs + * - Have had all their dependencies satisfied + * - Are from the right stream + * + * For any other case, leave it to the normal instruction + * issue below to handle them. + */ + if (!inFUMemInsts->empty() && lsq.canRequest()) { + DPRINTF(MinorExecute, "Trying to commit from mem FUs\n"); + + const MinorDynInstPtr head_mem_ref_inst = + inFUMemInsts->front().inst; + FUPipeline *fu = funcUnits[head_mem_ref_inst->fuIndex]; + const MinorDynInstPtr &fu_inst = fu->front().inst; + + /* Use this, possibly out of order, inst as the one + * to 'commit'/send to the LSQ */ + if (!fu_inst->isBubble() && + !fu_inst->inLSQ && + fu_inst->canEarlyIssue && + streamSeqNum == fu_inst->id.streamSeqNum && + head_exec_seq_num > fu_inst->instToWaitFor) + { + DPRINTF(MinorExecute, "Issuing mem ref early" + " inst: %s instToWaitFor: %d\n", + *(fu_inst), fu_inst->instToWaitFor); + + inst = fu_inst; + try_to_commit = true; + early_memory_issue = true; + completed_inst = true; + } + } + + /* Try and commit FU-less insts */ + if (!completed_inst && inst->isNoCostInst()) { + DPRINTF(MinorExecute, "Committing no cost inst: %s", *inst); + + try_to_commit = true; + completed_inst = true; + } + + /* Try to issue from the ends of FUs and the inFlightInsts + * queue */ + if (!completed_inst && !inst->inLSQ) { + DPRINTF(MinorExecute, "Trying to commit from FUs\n"); + + /* Try to commit from a functional unit */ + /* Is the head inst of the expected inst's FU actually the + * expected inst? */ + QueuedInst &fu_inst = + funcUnits[inst->fuIndex]->front(); + InstSeqNum fu_inst_seq_num = fu_inst.inst->id.execSeqNum; + + if (fu_inst.inst->isBubble()) { + /* No instruction ready */ + completed_inst = false; + } else if (fu_inst_seq_num != head_exec_seq_num) { + /* Past instruction: we must have already executed it + * in the same cycle and so the head inst isn't + * actually at the end of its pipeline + * Future instruction: handled above and only for + * mem refs on their way to the LSQ */ + } else /* if (fu_inst_seq_num == head_exec_seq_num) */ { + /* All instructions can be committed if they have the + * right execSeqNum and there are no in-flight + * mem insts before us */ + try_to_commit = true; + completed_inst = true; + } + } + + if (try_to_commit) { + discard_inst = inst->id.streamSeqNum != streamSeqNum || + discard; + + /* Is this instruction discardable as its streamSeqNum + * doesn't match? */ + if (!discard_inst) { + /* Try to commit or discard a non-memory instruction. + * Memory ops are actually 'committed' from this FUs + * and 'issued' into the memory system so we need to + * account for them later (commit_was_mem_issue gets + * set) */ + if (inst->extraCommitDelayExpr) { + DPRINTF(MinorExecute, "Evaluating expression for" + " extra commit delay inst: %s\n", *inst); + + ThreadContext *thread = + cpu.getContext(inst->id.threadId); + + TimingExprEvalContext context(inst->staticInst, + thread, NULL); + + uint64_t extra_delay = inst->extraCommitDelayExpr-> + eval(context); + + DPRINTF(MinorExecute, "Extra commit delay expr" + " result: %d\n", extra_delay); + + if (extra_delay < 128) { + inst->extraCommitDelay += Cycles(extra_delay); + } else { + DPRINTF(MinorExecute, "Extra commit delay was" + " very long: %d\n", extra_delay); + } + inst->extraCommitDelayExpr = NULL; + } + + /* Move the extraCommitDelay from the instruction + * into the minimumCommitCycle */ + if (inst->extraCommitDelay != Cycles(0)) { + inst->minimumCommitCycle = cpu.curCycle() + + inst->extraCommitDelay; + inst->extraCommitDelay = Cycles(0); + } + + /* @todo Think about making lastMemBarrier be + * MAX_UINT_64 to avoid using 0 as a marker value */ + if (!inst->isFault() && inst->isMemRef() && + lsq.getLastMemBarrier() < + inst->id.execSeqNum && + lsq.getLastMemBarrier() != 0) + { + DPRINTF(MinorExecute, "Not committing inst: %s yet" + " as there are incomplete barriers in flight\n", + *inst); + completed_inst = false; + } else if (inst->minimumCommitCycle > now) { + DPRINTF(MinorExecute, "Not committing inst: %s yet" + " as it wants to be stalled for %d more cycles\n", + *inst, inst->minimumCommitCycle - now); + completed_inst = false; + } else { + completed_inst = commitInst(inst, + early_memory_issue, branch, fault, + committed_inst, issued_mem_ref); + } + } else { + /* Discard instruction */ + completed_inst = true; + } + + if (completed_inst) { + /* Allow the pipeline to advance. If the FU head + * instruction wasn't the inFlightInsts head + * but had already been committed, it would have + * unstalled the pipeline before here */ + if (inst->fuIndex != noCostFUIndex) + funcUnits[inst->fuIndex]->stalled = false; + } + } + } else { + DPRINTF(MinorExecute, "No instructions to commit\n"); + completed_inst = false; + } + + /* All discardable instructions must also be 'completed' by now */ + assert(!(discard_inst && !completed_inst)); + + /* Instruction committed but was discarded due to streamSeqNum + * mismatch */ + if (discard_inst) { + DPRINTF(MinorExecute, "Discarding inst: %s as its stream" + " state was unexpected, expected: %d\n", + *inst, streamSeqNum); + + if (fault == NoFault) + cpu.stats.numDiscardedOps++; + } + + /* Mark the mem inst as being in the LSQ */ + if (issued_mem_ref) { + inst->fuIndex = 0; + inst->inLSQ = true; + } + + /* Pop issued (to LSQ) and discarded mem refs from the inFUMemInsts + * as they've *definitely* exited the FUs */ + if (completed_inst && inst->isMemRef()) { + /* The MemRef could have been discarded from the FU or the memory + * queue, so just check an FU instruction */ + if (!inFUMemInsts->empty() && + inFUMemInsts->front().inst == inst) + { + inFUMemInsts->pop(); + } + } + + if (completed_inst && !(issued_mem_ref && fault == NoFault)) { + /* Note that this includes discarded insts */ + DPRINTF(MinorExecute, "Completed inst: %s\n", *inst); + + /* Got to the end of a full instruction? */ + lastCommitWasEndOfMacroop = inst->isFault() || + inst->isLastOpInInst(); + + /* lastPredictionSeqNum is kept as a convenience to prevent its + * value from changing too much on the minorview display */ + lastPredictionSeqNum = inst->id.predictionSeqNum; + + /* Finished with the inst, remove it from the inst queue and + * clear its dependencies */ + inFlightInsts->pop(); + + /* Complete barriers in the LSQ/move to store buffer */ + if (inst->isInst() && inst->staticInst->isMemBarrier()) { + DPRINTF(MinorMem, "Completing memory barrier" + " inst: %s committed: %d\n", *inst, committed_inst); + lsq.completeMemBarrierInst(inst, committed_inst); + } + + scoreboard.clearInstDests(inst, inst->isMemRef()); + } + + /* Handle per-cycle instruction counting */ + if (committed_inst) { + bool is_no_cost_inst = inst->isNoCostInst(); + + /* Don't show no cost instructions as having taken a commit + * slot */ + if (DTRACE(MinorTrace) && !is_no_cost_inst) + instsBeingCommitted.insts[num_insts_committed] = inst; + + if (!is_no_cost_inst) + num_insts_committed++; + + if (num_insts_committed == commitLimit) + DPRINTF(MinorExecute, "Reached inst commit limit\n"); + + /* Re-set the time of the instruction if that's required for + * tracing */ + if (inst->traceData) { + if (setTraceTimeOnCommit) + inst->traceData->setWhen(curTick()); + inst->traceData->dump(); + } + + if (completed_mem_ref) + num_mem_refs_committed++; + + if (num_mem_refs_committed == memoryCommitLimit) + DPRINTF(MinorExecute, "Reached mem ref commit limit\n"); + } + } +} + +bool +Execute::isInbetweenInsts() const +{ + return lastCommitWasEndOfMacroop && + !lsq.accessesInFlight(); +} + +void +Execute::evaluate() +{ + inputBuffer.setTail(*inp.outputWire); + BranchData &branch = *out.inputWire; + + const ForwardInstData *insts_in = getInput(); + + /* Do all the cycle-wise activities for dcachePort here to potentially + * free up input spaces in the LSQ's requests queue */ + lsq.step(); + + /* Has an interrupt been signalled? This may not be acted on + * straighaway so this is different from took_interrupt below */ + bool interrupted = false; + /* If there was an interrupt signalled, was it acted on now? */ + bool took_interrupt = false; + + if (cpu.getInterruptController()) { + /* This is here because it seems that after drainResume the + * interrupt controller isn't always set */ + interrupted = drainState == NotDraining && isInterrupted(0); + } else { + DPRINTF(MinorInterrupt, "No interrupt controller\n"); + } + + unsigned int num_issued = 0; + + if (DTRACE(MinorTrace)) { + /* Empty the instsBeingCommitted for MinorTrace */ + instsBeingCommitted.bubbleFill(); + } + + /* THREAD threadId on isInterrupted */ + /* Act on interrupts */ + if (interrupted && isInbetweenInsts()) { + took_interrupt = takeInterrupt(0, branch); + /* Clear interrupted if no interrupt was actually waiting */ + interrupted = took_interrupt; + } + + if (took_interrupt) { + /* Do no commit/issue this cycle */ + } else if (!branch.isBubble()) { + /* It's important that this is here to carry Fetch1 wakeups to Fetch1 + * without overwriting them */ + DPRINTF(MinorInterrupt, "Execute skipping a cycle to allow old" + " branch to complete\n"); + } else { + if (interrupted) { + if (inFlightInsts->empty()) { + DPRINTF(MinorInterrupt, "Waiting but no insts\n"); + } else { + DPRINTF(MinorInterrupt, "Waiting for end of inst before" + " signalling interrupt\n"); + } + } + + /* commit can set stalled flags observable to issue and so *must* be + * called first */ + if (drainState != NotDraining) { + if (drainState == DrainCurrentInst) { + /* Commit only micro-ops, don't kill anything else */ + commit(true, false, branch); + + if (isInbetweenInsts()) + setDrainState(DrainHaltFetch); + + /* Discard any generated branch */ + branch = BranchData::bubble(); + } else if (drainState == DrainAllInsts) { + /* Kill all instructions */ + while (getInput()) + popInput(); + commit(false, true, branch); + } + } else { + /* Commit micro-ops only if interrupted. Otherwise, commit + * anything you like */ + commit(interrupted, false, branch); + } + + /* This will issue merrily even when interrupted in the sure and + * certain knowledge that the interrupt with change the stream */ + if (insts_in) + num_issued = issue(false); + } + + /* Halt fetch, but don't do it until we have the current instruction in + * the bag */ + if (drainState == DrainHaltFetch) { + updateBranchData(BranchData::HaltFetch, MinorDynInst::bubble(), + TheISA::PCState(0), branch); + + cpu.wakeupOnEvent(Pipeline::ExecuteStageId); + setDrainState(DrainAllInsts); + } + + MinorDynInstPtr next_issuable_inst = NULL; + bool can_issue_next = false; + + /* Find the next issuable instruction and see if it can be issued */ + if (getInput()) { + MinorDynInstPtr inst = getInput()->insts[inputIndex]; + + if (inst->isFault()) { + can_issue_next = true; + } else if (!inst->isBubble()) { + if (cpu.getContext(inst->id.threadId)->status() != + ThreadContext::Suspended) + { + next_issuable_inst = inst; + } + } + } + + bool becoming_stalled = true; + + /* Advance the pipelines and note whether they still need to be + * advanced */ + for (unsigned int i = 0; i < numFuncUnits; i++) { + FUPipeline *fu = funcUnits[i]; + + fu->advance(); + + /* If we need to go again, the pipeline will have been left or set + * to be unstalled */ + if (fu->occupancy != 0 && !fu->stalled) + becoming_stalled = false; + + /* Could we possibly issue the next instruction? This is quite + * an expensive test */ + if (next_issuable_inst && !fu->stalled && + scoreboard.canInstIssue(next_issuable_inst, + NULL, NULL, cpu.curCycle() + Cycles(1), + cpu.getContext(next_issuable_inst->id.threadId)) && + fu->provides(next_issuable_inst->staticInst->opClass())) + { + can_issue_next = true; + } + } + + bool head_inst_might_commit = false; + + /* Could the head in flight insts be committed */ + if (!inFlightInsts->empty()) { + const QueuedInst &head_inst = inFlightInsts->front(); + + if (head_inst.inst->isNoCostInst()) { + head_inst_might_commit = true; + } else { + FUPipeline *fu = funcUnits[head_inst.inst->fuIndex]; + + /* Head inst is commitable */ + if ((fu->stalled && + fu->front().inst->id == head_inst.inst->id) || + lsq.findResponse(head_inst.inst)) + { + head_inst_might_commit = true; + } + } + } + + DPRINTF(Activity, "Need to tick num issued insts: %s%s%s%s%s%s\n", + (num_issued != 0 ? " (issued some insts)" : ""), + (becoming_stalled ? " (becoming stalled)" : "(not becoming stalled)"), + (can_issue_next ? " (can issued next inst)" : ""), + (head_inst_might_commit ? "(head inst might commit)" : ""), + (lsq.needsToTick() ? " (LSQ needs to tick)" : ""), + (interrupted ? " (interrupted)" : "")); + + bool need_to_tick = + num_issued != 0 || /* Issued some insts this cycle */ + !becoming_stalled || /* Some FU pipelines can still move */ + can_issue_next || /* Can still issue a new inst */ + head_inst_might_commit || /* Could possible commit the next inst */ + lsq.needsToTick() || /* Must step the dcache port */ + interrupted; /* There are pending interrupts */ + + if (!need_to_tick) { + DPRINTF(Activity, "The next cycle might be skippable as there are no" + " advanceable FUs\n"); + } + + /* Wake up if we need to tick again */ + if (need_to_tick) + cpu.wakeupOnEvent(Pipeline::ExecuteStageId); + + /* Note activity of following buffer */ + if (!branch.isBubble()) + cpu.activityRecorder->activity(); + + /* Make sure the input (if any left) is pushed */ + inputBuffer.pushTail(); +} + +void +Execute::wakeupFetch(BranchData::Reason reason) +{ + BranchData branch; + assert(branch.isBubble()); + + /* THREAD thread id */ + ThreadContext *thread = cpu.getContext(0); + + /* Force a branch to the current PC (which should be the next inst.) to + * wake up Fetch1 */ + if (!branch.isStreamChange() /* No real branch already happened */) { + DPRINTF(MinorInterrupt, "Waking up Fetch (via Execute) by issuing" + " a branch: %s\n", thread->pcState()); + + assert(thread->pcState().microPC() == 0); + + updateBranchData(reason, + MinorDynInst::bubble(), thread->pcState(), branch); + } else { + DPRINTF(MinorInterrupt, "Already branching, no need for wakeup\n"); + } + + *out.inputWire = branch; + + /* Make sure we get ticked */ + cpu.wakeupOnEvent(Pipeline::ExecuteStageId); +} + +void +Execute::minorTrace() const +{ + std::ostringstream insts; + std::ostringstream stalled; + + instsBeingCommitted.reportData(insts); + lsq.minorTrace(); + inputBuffer.minorTrace(); + scoreboard.minorTrace(); + + /* Report functional unit stalling in one string */ + unsigned int i = 0; + while (i < numFuncUnits) + { + stalled << (funcUnits[i]->stalled ? '1' : 'E'); + i++; + if (i != numFuncUnits) + stalled << ','; + } + + MINORTRACE("insts=%s inputIndex=%d streamSeqNum=%d" + " stalled=%s drainState=%d isInbetweenInsts=%d\n", + insts.str(), inputIndex, streamSeqNum, stalled.str(), drainState, + isInbetweenInsts()); + + std::for_each(funcUnits.begin(), funcUnits.end(), + std::mem_fun(&FUPipeline::minorTrace)); + + inFlightInsts->minorTrace(); + inFUMemInsts->minorTrace(); +} + +void +Execute::drainResume() +{ + DPRINTF(Drain, "MinorExecute drainResume\n"); + + setDrainState(NotDraining); + + /* Wakeup fetch and keep the pipeline running until that branch takes + * effect */ + wakeupFetch(BranchData::WakeupFetch); + cpu.wakeupOnEvent(Pipeline::ExecuteStageId); +} + +std::ostream &operator <<(std::ostream &os, Execute::DrainState state) +{ + switch (state) + { + case Execute::NotDraining: + os << "NotDraining"; + break; + case Execute::DrainCurrentInst: + os << "DrainCurrentInst"; + break; + case Execute::DrainHaltFetch: + os << "DrainHaltFetch"; + break; + case Execute::DrainAllInsts: + os << "DrainAllInsts"; + break; + default: + os << "Drain-" << static_cast<int>(state); + break; + } + + return os; +} + +void +Execute::setDrainState(DrainState state) +{ + DPRINTF(Drain, "setDrainState: %s\n", state); + drainState = state; +} + +unsigned int +Execute::drain() +{ + DPRINTF(Drain, "MinorExecute drain\n"); + + if (drainState == NotDraining) { + cpu.wakeupOnEvent(Pipeline::ExecuteStageId); + + /* Go to DrainCurrentInst if we're not between operations + * this should probably test the LSQ as well. Or maybe + * just always go to DrainCurrentInst anyway */ + if (lastCommitWasEndOfMacroop) + setDrainState(DrainHaltFetch); + else + setDrainState(DrainCurrentInst); + } + + return (isDrained() ? 0 : 1); +} + +bool +Execute::isDrained() +{ + return drainState == DrainAllInsts && + inputBuffer.empty() && + inFlightInsts->empty() && + lsq.isDrained(); +} + +Execute::~Execute() +{ + for (unsigned int i = 0; i < numFuncUnits; i++) + delete funcUnits[i]; + + delete inFlightInsts; +} + +bool +Execute::instIsRightStream(MinorDynInstPtr inst) +{ + return inst->id.streamSeqNum == streamSeqNum; +} + +bool +Execute::instIsHeadInst(MinorDynInstPtr inst) +{ + bool ret = false; + + if (!inFlightInsts->empty()) + ret = inFlightInsts->front().inst->id == inst->id; + + return ret; +} + +MinorCPU::MinorCPUPort & +Execute::getDcachePort() +{ + return lsq.getDcachePort(); +} + +} diff --git a/src/cpu/minor/execute.hh b/src/cpu/minor/execute.hh new file mode 100644 index 000000000..8cd026534 --- /dev/null +++ b/src/cpu/minor/execute.hh @@ -0,0 +1,321 @@ +/* + * Copyright (c) 2013-2014 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: Andrew Bardsley + */ + +/** + * @file + * + * All the fun of executing instructions from Decode and sending branch/new + * instruction stream info. to Fetch1. + */ + +#ifndef __CPU_MINOR_EXECUTE_HH__ +#define __CPU_MINOR_EXECUTE_HH__ + +#include "cpu/minor/buffers.hh" +#include "cpu/minor/cpu.hh" +#include "cpu/minor/func_unit.hh" +#include "cpu/minor/lsq.hh" +#include "cpu/minor/pipe_data.hh" +#include "cpu/minor/scoreboard.hh" + +namespace Minor +{ + +/** Execute stage. Everything apart from fetching and decoding instructions. + * The LSQ lives here too. */ +class Execute : public Named +{ + protected: + /** Input port carrying instructions from Decode */ + Latch<ForwardInstData>::Output inp; + + /** Input port carrying stream changes to Fetch1 */ + Latch<BranchData>::Input out; + + /** Pointer back to the containing CPU */ + MinorCPU &cpu; + + /** Number of instructions that can be issued per cycle */ + unsigned int issueLimit; + + /** Number of memory ops that can be issued per cycle */ + unsigned int memoryIssueLimit; + + /** Number of instructions that can be committed per cycle */ + unsigned int commitLimit; + + /** Number of memory instructions that can be committed per cycle */ + unsigned int memoryCommitLimit; + + /** If true, more than one input line can be processed each cycle if + * there is room to execute more instructions than taken from the first + * line */ + bool processMoreThanOneInput; + + /** Descriptions of the functional units we want to generate */ + MinorFUPool &fuDescriptions; + + /** Number of functional units to produce */ + unsigned int numFuncUnits; + + /** Longest latency of any FU, useful for setting up the activity + * recoder */ + Cycles longestFuLatency; + + /** Modify instruction trace times on commit */ + bool setTraceTimeOnCommit; + + /** Modify instruction trace times on issue */ + bool setTraceTimeOnIssue; + + /** Allow mem refs to leave their FUs before reaching the head + * of the in flight insts queue if their dependencies are met */ + bool allowEarlyMemIssue; + + /** The FU index of the non-existent costless FU for instructions + * which pass the MinorDynInst::isNoCostInst test */ + unsigned int noCostFUIndex; + + /** Dcache port to pass on to the CPU. Execute owns this */ + LSQ lsq; + + /** Scoreboard of instruction dependencies */ + Scoreboard scoreboard; + + /** The execution functional units */ + std::vector<FUPipeline *> funcUnits; + + public: /* Public for Pipeline to be able to pass it to Decode */ + InputBuffer<ForwardInstData> inputBuffer; + + protected: + /** Stage cycle-by-cycle state */ + + /** State that drain passes through (in order). On a drain request, + * Execute transitions into either DrainCurrentInst (if between + * microops) or DrainHaltFetch. + * + * Note that Execute doesn't actually have * a 'Drained' state, only + * an indication that it's currently draining and isDrained that can't + * tell if there are insts still in the pipeline leading up to + * Execute */ + enum DrainState + { + NotDraining, /* Not draining, possibly running */ + DrainCurrentInst, /* Draining to end of inst/macroop */ + DrainHaltFetch, /* Halting Fetch after completing current inst */ + DrainAllInsts /* Discarding all remaining insts */ + }; + + /** In-order instructions either in FUs or the LSQ */ + Queue<QueuedInst, ReportTraitsAdaptor<QueuedInst> > *inFlightInsts; + + /** Memory ref instructions still in the FUs */ + Queue<QueuedInst, ReportTraitsAdaptor<QueuedInst> > *inFUMemInsts; + + /** Index that we've completed upto in getInput data. We can say we're + * popInput when this equals getInput()->width() */ + unsigned int inputIndex; + + /** The last commit was the end of a full instruction so an interrupt + * can safely happen */ + bool lastCommitWasEndOfMacroop; + + /** Structure for reporting insts currently being processed/retired + * for MinorTrace */ + ForwardInstData instsBeingCommitted; + + /** Source of sequence number for instuction streams. Increment this and + * pass to fetch whenever an instruction stream needs to be changed. + * For any more complicated behaviour (e.g. speculation) there'll need + * to be another plan. THREAD, need one for each thread */ + InstSeqNum streamSeqNum; + + /** A prediction number for use where one isn't available from an + * instruction. This is harvested from committed instructions. + * This isn't really needed as the streamSeqNum will change on + * a branch, but it minimises disruption in stream identification */ + InstSeqNum lastPredictionSeqNum; + + /** State progression for draining NotDraining -> ... -> DrainAllInsts */ + DrainState drainState; + + protected: + friend std::ostream &operator <<(std::ostream &os, DrainState state); + + /** Get a piece of data to work on from the inputBuffer, or 0 if there + * is no data. */ + const ForwardInstData *getInput(); + + /** Pop an element off the input buffer, if there are any */ + void popInput(); + + /** Generate Branch data based (into branch) on an observed (or not) + * change in PC while executing an instruction. + * Also handles branch prediction information within the inst. */ + void tryToBranch(MinorDynInstPtr inst, Fault fault, BranchData &branch); + + /** Actually create a branch to communicate to Fetch1/Fetch2 and, + * if that is a stream-changing branch update the streamSeqNum */ + void updateBranchData(BranchData::Reason reason, + MinorDynInstPtr inst, const TheISA::PCState &target, + BranchData &branch); + + /** Handle extracting mem ref responses from the memory queues and + * completing the associated instructions. + * Fault is an output and will contain any fault caused (and already + * invoked by the function) + * Sets branch to any branch generated by the instruction. */ + void handleMemResponse(MinorDynInstPtr inst, + LSQ::LSQRequestPtr response, BranchData &branch, + Fault &fault); + + /** Execute a memory reference instruction. This calls initiateAcc on + * the instruction which will then call writeMem or readMem to issue a + * memory access to the LSQ. + * Returns true if the instruction was executed rather than stalled + * because of a lack of LSQ resources and false otherwise. + * branch is set to any branch raised by the instruction. + * failed_predicate is set to false if the instruction passed its + * predicate and so will access memory or true if the instruction + * *failed* its predicate and is now complete. + * fault is set if any non-NoFault fault is raised. + * Any faults raised are actually invoke-d by this function. */ + bool executeMemRefInst(MinorDynInstPtr inst, BranchData &branch, + bool &failed_predicate, Fault &fault); + + /** Has an interrupt been raised */ + bool isInterrupted(ThreadID thread_id) const; + + /** Are we between instructions? Can we be interrupted? */ + bool isInbetweenInsts() const; + + /** Act on an interrupt. Returns true if an interrupt was actually + * signalled and invoked */ + bool takeInterrupt(ThreadID thread_id, BranchData &branch); + + /** Try and issue instructions from the inputBuffer */ + unsigned int issue(bool only_issue_microops); + + /** Try to act on PC-related events. Returns true if any were + * executed */ + bool tryPCEvents(); + + /** Do the stats handling and instruction count and PC event events + * related to the new instruction/op counts */ + void doInstCommitAccounting(MinorDynInstPtr inst); + + /** Commit a single instruction. Returns true if the instruction being + * examined was completed (fully executed, discarded, or initiated a + * memory access), false if there is still some processing to do. + * fu_index is the index of the functional unit this instruction is + * being executed in into for funcUnits + * If early_memory_issue is true then this is an early execution + * of a mem ref and so faults will not be processed. + * If the return value is true: + * fault is set if a fault happened, + * branch is set to indicate any branch that occurs + * committed is set to true if this instruction is committed + * (and so needs to be traced and accounted for) + * completed_mem_issue is set if the instruction was a + * memory access that was issued */ + bool commitInst(MinorDynInstPtr inst, bool early_memory_issue, + BranchData &branch, Fault &fault, bool &committed, + bool &completed_mem_issue); + + /** Try and commit instructions from the ends of the functional unit + * pipelines. + * If only_commit_microops is true then only commit upto the + * end of the currect full instruction. + * If discard is true then discard all instructions rather than + * committing. + * branch is set to any branch raised during commit. */ + void commit(bool only_commit_microops, bool discard, BranchData &branch); + + /** Set the drain state (with useful debugging messages) */ + void setDrainState(DrainState state); + + public: + Execute(const std::string &name_, + MinorCPU &cpu_, + MinorCPUParams ¶ms, + Latch<ForwardInstData>::Output inp_, + Latch<BranchData>::Input out_); + + ~Execute(); + + public: + + /** Cause Execute to issue an UnpredictedBranch (or WakeupFetch if + * that was passed as the reason) to Fetch1 to wake the + * system up (using the PC from the thread context). */ + void wakeupFetch(BranchData::Reason reason = + BranchData::UnpredictedBranch); + + /** Returns the DcachePort owned by this Execute to pass upwards */ + MinorCPU::MinorCPUPort &getDcachePort(); + + /** To allow ExecContext to find the LSQ */ + LSQ &getLSQ() { return lsq; } + + /** Does the given instruction have the right stream sequence number + * to be committed? */ + bool instIsRightStream(MinorDynInstPtr inst); + + /** Returns true if the given instruction is at the head of the + * inFlightInsts instruction queue */ + bool instIsHeadInst(MinorDynInstPtr inst); + + /** Pass on input/buffer data to the output if you can */ + void evaluate(); + + void minorTrace() const; + + /** After thread suspension, has Execute been drained of in-flight + * instructions and memory accesses. */ + bool isDrained(); + + /** Like the drain interface on SimObject */ + unsigned int drain(); + void drainResume(); +}; + +} + +#endif /* __CPU_MINOR_EXECUTE_HH__ */ diff --git a/src/cpu/minor/fetch1.cc b/src/cpu/minor/fetch1.cc new file mode 100644 index 000000000..45dc5eddc --- /dev/null +++ b/src/cpu/minor/fetch1.cc @@ -0,0 +1,676 @@ +/* + * Copyright (c) 2013-2014 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: Andrew Bardsley + */ + +#include <cstring> +#include <iomanip> +#include <sstream> + +#include "base/cast.hh" +#include "cpu/minor/fetch1.hh" +#include "cpu/minor/pipeline.hh" +#include "debug/Drain.hh" +#include "debug/Fetch.hh" +#include "debug/MinorTrace.hh" + +namespace Minor +{ + +Fetch1::Fetch1(const std::string &name_, + MinorCPU &cpu_, + MinorCPUParams ¶ms, + Latch<BranchData>::Output inp_, + Latch<ForwardLineData>::Input out_, + Latch<BranchData>::Output prediction_, + Reservable &next_stage_input_buffer) : + Named(name_), + cpu(cpu_), + inp(inp_), + out(out_), + prediction(prediction_), + nextStageReserve(next_stage_input_buffer), + icachePort(name_ + ".icache_port", *this, cpu_), + lineSnap(params.fetch1LineSnapWidth), + maxLineWidth(params.fetch1LineWidth), + fetchLimit(params.fetch1FetchLimit), + state(FetchWaitingForPC), + pc(0), + streamSeqNum(InstId::firstStreamSeqNum), + predictionSeqNum(InstId::firstPredictionSeqNum), + blocked(false), + requests(name_ + ".requests", "lines", params.fetch1FetchLimit), + transfers(name_ + ".transfers", "lines", params.fetch1FetchLimit), + icacheState(IcacheRunning), + lineSeqNum(InstId::firstLineSeqNum), + numFetchesInMemorySystem(0), + numFetchesInITLB(0) +{ + if (lineSnap == 0) { + lineSnap = cpu.cacheLineSize(); + DPRINTF(Fetch, "lineSnap set to cache line size of: %d\n", + lineSnap); + } + + if (maxLineWidth == 0) { + maxLineWidth = cpu.cacheLineSize(); + DPRINTF(Fetch, "maxLineWidth set to cache line size of: %d\n", + maxLineWidth); + } + + /* These assertions should be copied to the Python config. as well */ + if ((lineSnap % sizeof(TheISA::MachInst)) != 0) { + fatal("%s: fetch1LineSnapWidth must be a multiple " + "of sizeof(TheISA::MachInst) (%d)\n", name_, + sizeof(TheISA::MachInst)); + } + + if (!(maxLineWidth >= lineSnap && + (maxLineWidth % sizeof(TheISA::MachInst)) == 0)) + { + fatal("%s: fetch1LineWidth must be a multiple of" + " sizeof(TheISA::MachInst)" + " (%d), and >= fetch1LineSnapWidth (%d)\n", + name_, sizeof(TheISA::MachInst), lineSnap); + } + + if (fetchLimit < 1) { + fatal("%s: fetch1FetchLimit must be >= 1 (%d)\n", name_, + fetchLimit); + } +} + +void +Fetch1::fetchLine() +{ + /* If line_offset != 0, a request is pushed for the remainder of the + * line. */ + /* Use a lower, sizeof(MachInst) aligned address for the fetch */ + Addr aligned_pc = pc.instAddr() & ~((Addr) lineSnap - 1); + unsigned int line_offset = aligned_pc % lineSnap; + unsigned int request_size = maxLineWidth - line_offset; + + /* Fill in the line's id */ + InstId request_id(0 /* thread */, + streamSeqNum, predictionSeqNum, + lineSeqNum); + + FetchRequestPtr request = new FetchRequest(*this, request_id, pc); + + DPRINTF(Fetch, "Inserting fetch into the fetch queue " + "%s addr: 0x%x pc: %s line_offset: %d request_size: %d\n", + request_id, aligned_pc, pc, line_offset, request_size); + + request->request.setThreadContext(cpu.cpuId(), /* thread id */ 0); + request->request.setVirt(0 /* asid */, + aligned_pc, request_size, Request::INST_FETCH, cpu.instMasterId(), + /* I've no idea why we need the PC, but give it */ + pc.instAddr()); + + DPRINTF(Fetch, "Submitting ITLB request\n"); + numFetchesInITLB++; + + request->state = FetchRequest::InTranslation; + + /* Reserve space in the queues upstream of requests for results */ + transfers.reserve(); + requests.push(request); + + /* Submit the translation request. The response will come + * through finish/markDelayed on this request as it bears + * the Translation interface */ + cpu.threads[request->id.threadId]->itb->translateTiming( + &request->request, + cpu.getContext(request->id.threadId), + request, BaseTLB::Execute); + + lineSeqNum++; + + /* Step the PC for the next line onto the line aligned next address. + * Note that as instructions can span lines, this PC is only a + * reliable 'new' PC if the next line has a new stream sequence number. */ +#if THE_ISA == ALPHA_ISA + /* Restore the low bits of the PC used as address space flags */ + Addr pc_low_bits = pc.instAddr() & + ((Addr) (1 << sizeof(TheISA::MachInst)) - 1); + + pc.set(aligned_pc + request_size + pc_low_bits); +#else + pc.set(aligned_pc + request_size); +#endif +} + +std::ostream & +operator <<(std::ostream &os, Fetch1::IcacheState state) +{ + switch (state) { + case Fetch1::IcacheRunning: + os << "IcacheRunning"; + break; + case Fetch1::IcacheNeedsRetry: + os << "IcacheNeedsRetry"; + break; + default: + os << "IcacheState-" << static_cast<int>(state); + break; + } + return os; +} + +void +Fetch1::FetchRequest::makePacket() +{ + /* Make the necessary packet for a memory transaction */ + packet = new Packet(&request, MemCmd::ReadReq); + packet->allocate(); + + /* This FetchRequest becomes SenderState to allow the response to be + * identified */ + packet->pushSenderState(this); +} + +void +Fetch1::FetchRequest::finish( + Fault fault_, RequestPtr request_, ThreadContext *tc, BaseTLB::Mode mode) +{ + fault = fault_; + + state = Translated; + fetch.handleTLBResponse(this); + + /* Let's try and wake up the processor for the next cycle */ + fetch.cpu.wakeupOnEvent(Pipeline::Fetch1StageId); +} + +void +Fetch1::handleTLBResponse(FetchRequestPtr response) +{ + numFetchesInITLB--; + + if (response->fault != NoFault) { + DPRINTF(Fetch, "Fault in address ITLB translation: %s, " + "paddr: 0x%x, vaddr: 0x%x\n", + response->fault->name(), + (response->request.hasPaddr() ? response->request.getPaddr() : 0), + response->request.getVaddr()); + + if (DTRACE(MinorTrace)) + minorTraceResponseLine(name(), response); + } else { + DPRINTF(Fetch, "Got ITLB response\n"); + } + + response->state = FetchRequest::Translated; + + tryToSendToTransfers(response); +} + +Fetch1::FetchRequest::~FetchRequest() +{ + if (packet) + delete packet; +} + +void +Fetch1::tryToSendToTransfers(FetchRequestPtr request) +{ + if (!requests.empty() && requests.front() != request) { + DPRINTF(Fetch, "Fetch not at front of requests queue, can't" + " issue to memory\n"); + return; + } + + if (request->state == FetchRequest::InTranslation) { + DPRINTF(Fetch, "Fetch still in translation, not issuing to" + " memory\n"); + return; + } + + if (request->isDiscardable() || request->fault != NoFault) { + /* Discarded and faulting requests carry on through transfers + * as Complete/packet == NULL */ + + request->state = FetchRequest::Complete; + moveFromRequestsToTransfers(request); + + /* Wake up the pipeline next cycle as there will be no event + * for this queue->queue transfer */ + cpu.wakeupOnEvent(Pipeline::Fetch1StageId); + } else if (request->state == FetchRequest::Translated) { + if (!request->packet) + request->makePacket(); + + /* Ensure that the packet won't delete the request */ + assert(request->packet->needsResponse()); + + if (tryToSend(request)) + moveFromRequestsToTransfers(request); + } else { + DPRINTF(Fetch, "Not advancing line fetch\n"); + } +} + +void +Fetch1::moveFromRequestsToTransfers(FetchRequestPtr request) +{ + assert(!requests.empty() && requests.front() == request); + + requests.pop(); + transfers.push(request); +} + +bool +Fetch1::tryToSend(FetchRequestPtr request) +{ + bool ret = false; + + if (icachePort.sendTimingReq(request->packet)) { + /* Invalidate the fetch_requests packet so we don't + * accidentally fail to deallocate it (or use it!) + * later by overwriting it */ + request->packet = NULL; + request->state = FetchRequest::RequestIssuing; + numFetchesInMemorySystem++; + + ret = true; + + DPRINTF(Fetch, "Issued fetch request to memory: %s\n", + request->id); + } else { + /* Needs to be resent, wait for that */ + icacheState = IcacheNeedsRetry; + + DPRINTF(Fetch, "Line fetch needs to retry: %s\n", + request->id); + } + + return ret; +} + +void +Fetch1::stepQueues() +{ + IcacheState old_icache_state = icacheState; + + switch (icacheState) { + case IcacheRunning: + /* Move ITLB results on to the memory system */ + if (!requests.empty()) { + tryToSendToTransfers(requests.front()); + } + break; + case IcacheNeedsRetry: + break; + } + + if (icacheState != old_icache_state) { + DPRINTF(Fetch, "Step in state %s moving to state %s\n", + old_icache_state, icacheState); + } +} + +void +Fetch1::popAndDiscard(FetchQueue &queue) +{ + if (!queue.empty()) { + delete queue.front(); + queue.pop(); + } +} + +unsigned int +Fetch1::numInFlightFetches() +{ + return requests.occupiedSpace() + + transfers.occupiedSpace(); +} + +/** Print the appropriate MinorLine line for a fetch response */ +void +Fetch1::minorTraceResponseLine(const std::string &name, + Fetch1::FetchRequestPtr response) const +{ + Request &request M5_VAR_USED = response->request; + + if (response->packet && response->packet->isError()) { + MINORLINE(this, "id=F;%s vaddr=0x%x fault=\"error packet\"\n", + response->id, request.getVaddr()); + } else if (response->fault != NoFault) { + MINORLINE(this, "id=F;%s vaddr=0x%x fault=\"%s\"\n", + response->id, request.getVaddr(), response->fault->name()); + } else { + MINORLINE(this, "id=%s size=%d vaddr=0x%x paddr=0x%x\n", + response->id, request.getSize(), + request.getVaddr(), request.getPaddr()); + } +} + +bool +Fetch1::recvTimingResp(PacketPtr response) +{ + DPRINTF(Fetch, "recvTimingResp %d\n", numFetchesInMemorySystem); + + /* Only push the response if we didn't change stream? No, all responses + * should hit the responses queue. It's the job of 'step' to throw them + * away. */ + FetchRequestPtr fetch_request = safe_cast<FetchRequestPtr> + (response->popSenderState()); + + /* Fixup packet in fetch_request as this may have changed */ + assert(!fetch_request->packet); + fetch_request->packet = response; + + numFetchesInMemorySystem--; + fetch_request->state = FetchRequest::Complete; + + if (DTRACE(MinorTrace)) + minorTraceResponseLine(name(), fetch_request); + + if (response->isError()) { + DPRINTF(Fetch, "Received error response packet: %s\n", + fetch_request->id); + } + + /* We go to idle even if there are more things to do on the queues as + * it's the job of step to actually step us on to the next transaction */ + + /* Let's try and wake up the processor for the next cycle to move on + * queues */ + cpu.wakeupOnEvent(Pipeline::Fetch1StageId); + + /* Never busy */ + return true; +} + +void +Fetch1::recvRetry() +{ + DPRINTF(Fetch, "recvRetry\n"); + assert(icacheState == IcacheNeedsRetry); + assert(!requests.empty()); + + FetchRequestPtr retryRequest = requests.front(); + + icacheState = IcacheRunning; + + if (tryToSend(retryRequest)) + moveFromRequestsToTransfers(retryRequest); +} + +std::ostream & +operator <<(std::ostream &os, Fetch1::FetchState state) +{ + switch (state) { + case Fetch1::FetchHalted: + os << "FetchHalted"; + break; + case Fetch1::FetchWaitingForPC: + os << "FetchWaitingForPC"; + break; + case Fetch1::FetchRunning: + os << "FetchRunning"; + break; + default: + os << "FetchState-" << static_cast<int>(state); + break; + } + return os; +} + +void +Fetch1::changeStream(const BranchData &branch) +{ + updateExpectedSeqNums(branch); + + /* Start fetching again if we were stopped */ + switch (branch.reason) { + case BranchData::SuspendThread: + DPRINTF(Fetch, "Suspending fetch: %s\n", branch); + state = FetchWaitingForPC; + break; + case BranchData::HaltFetch: + DPRINTF(Fetch, "Halting fetch\n"); + state = FetchHalted; + break; + default: + DPRINTF(Fetch, "Changing stream on branch: %s\n", branch); + state = FetchRunning; + break; + } + pc = branch.target; +} + +void +Fetch1::updateExpectedSeqNums(const BranchData &branch) +{ + DPRINTF(Fetch, "Updating streamSeqNum from: %d to %d," + " predictionSeqNum from: %d to %d\n", + streamSeqNum, branch.newStreamSeqNum, + predictionSeqNum, branch.newPredictionSeqNum); + + /* Change the stream */ + streamSeqNum = branch.newStreamSeqNum; + /* Update the prediction. Note that it's possible for this to + * actually set the prediction to an *older* value if new + * predictions have been discarded by execute */ + predictionSeqNum = branch.newPredictionSeqNum; +} + +void +Fetch1::processResponse(Fetch1::FetchRequestPtr response, + ForwardLineData &line) +{ + PacketPtr packet = response->packet; + + /* Pass the prefetch abort (if any) on to Fetch2 in a ForwardLineData + * structure */ + line.setFault(response->fault); + /* Make sequence numbers valid in return */ + line.id = response->id; + /* Set PC to virtual address */ + line.pc = response->pc; + /* Set the lineBase, which is a sizeof(MachInst) aligned address <= + * pc.instAddr() */ + line.lineBaseAddr = response->request.getVaddr(); + + if (response->fault != NoFault) { + /* Stop fetching if there was a fault */ + /* Should probably try to flush the queues as well, but we + * can't be sure that this fault will actually reach Execute, and we + * can't (currently) selectively remove this stream from the queues */ + DPRINTF(Fetch, "Stopping line fetch because of fault: %s\n", + response->fault->name()); + state = Fetch1::FetchWaitingForPC; + } else { + line.adoptPacketData(packet); + /* Null the response's packet to prevent the response from trying to + * deallocate the packet */ + response->packet = NULL; + } +} + +void +Fetch1::evaluate() +{ + const BranchData &execute_branch = *inp.outputWire; + const BranchData &fetch2_branch = *prediction.outputWire; + ForwardLineData &line_out = *out.inputWire; + + assert(line_out.isBubble()); + + blocked = !nextStageReserve.canReserve(); + + /* Are we changing stream? Look to the Execute branches first, then + * to predicted changes of stream from Fetch2 */ + /* @todo, find better way to express ignoring branch predictions */ + if (execute_branch.isStreamChange() && + execute_branch.reason != BranchData::BranchPrediction) + { + if (state == FetchHalted) { + if (execute_branch.reason == BranchData::WakeupFetch) { + DPRINTF(Fetch, "Waking up fetch: %s\n", execute_branch); + changeStream(execute_branch); + } else { + DPRINTF(Fetch, "Halted, ignoring branch: %s\n", + execute_branch); + } + } else { + changeStream(execute_branch); + } + + if (!fetch2_branch.isBubble()) { + DPRINTF(Fetch, "Ignoring simultaneous prediction: %s\n", + fetch2_branch); + } + + /* The streamSeqNum tagging in request/response ->req should handle + * discarding those requests when we get to them. */ + } else if (state != FetchHalted && fetch2_branch.isStreamChange()) { + /* Handle branch predictions by changing the instruction source + * if we're still processing the same stream (as set by streamSeqNum) + * as the one of the prediction. + */ + if (fetch2_branch.newStreamSeqNum != streamSeqNum) { + DPRINTF(Fetch, "Not changing stream on prediction: %s," + " streamSeqNum mismatch\n", + fetch2_branch); + } else { + changeStream(fetch2_branch); + } + } + + /* Can we fetch? */ + /* The bare minimum requirements for initiating a fetch */ + /* THREAD need to handle multiple threads */ + if (state == FetchRunning && /* We are actually fetching */ + !blocked && /* Space in the Fetch2 inputBuffer */ + /* The thread we're going to fetch for (thread 0), is active */ + cpu.getContext(0)->status() == ThreadContext::Active && + numInFlightFetches() < fetchLimit) + { + fetchLine(); + /* Take up a slot in the fetch queue */ + nextStageReserve.reserve(); + } + + /* Halting shouldn't prevent fetches in flight from being processed */ + /* Step fetches through the icachePort queues and memory system */ + stepQueues(); + + /* As we've thrown away early lines, if there is a line, it must + * be from the right stream */ + if (!transfers.empty() && + transfers.front()->isComplete()) + { + Fetch1::FetchRequestPtr response = transfers.front(); + + if (response->isDiscardable()) { + nextStageReserve.freeReservation(); + + DPRINTF(Fetch, "Discarding translated fetch at it's for" + " an old stream\n"); + + /* Wake up next cycle just in case there was some other + * action to do */ + cpu.wakeupOnEvent(Pipeline::Fetch1StageId); + } else { + DPRINTF(Fetch, "Processing fetched line: %s\n", + response->id); + + processResponse(response, line_out); + } + + popAndDiscard(transfers); + } + + /* If we generated output, and mark the stage as being active + * to encourage that output on to the next stage */ + if (!line_out.isBubble()) + cpu.activityRecorder->activity(); + + /* Fetch1 has no inputBuffer so the only activity we can have is to + * generate a line output (tested just above) or to initiate a memory + * fetch which will signal activity when it returns/needs stepping + * between queues */ +} + +bool +Fetch1::isDrained() +{ + DPRINTF(Drain, "isDrained %s %s%s%s\n", + state == FetchHalted, + (numInFlightFetches() == 0 ? "" : "inFlightFetches "), + ((*out.inputWire).isBubble() ? "" : "outputtingLine")); + + return state == FetchHalted && + numInFlightFetches() == 0 && + (*out.inputWire).isBubble(); +} + +void +Fetch1::FetchRequest::reportData(std::ostream &os) const +{ + os << id; +} + +bool Fetch1::FetchRequest::isDiscardable() const +{ + /* Can't discard lines in TLB/memory */ + return state != InTranslation && state != RequestIssuing && + (id.streamSeqNum != fetch.streamSeqNum || + id.predictionSeqNum != fetch.predictionSeqNum); +} + +void +Fetch1::minorTrace() const +{ + std::ostringstream data; + + if (blocked) + data << 'B'; + else + (*out.inputWire).reportData(data); + + MINORTRACE("state=%s icacheState=%s in_tlb_mem=%s/%s" + " streamSeqNum=%d lines=%s\n", state, icacheState, + numFetchesInITLB, numFetchesInMemorySystem, + streamSeqNum, data.str()); + requests.minorTrace(); + transfers.minorTrace(); +} + +} diff --git a/src/cpu/minor/fetch1.hh b/src/cpu/minor/fetch1.hh new file mode 100644 index 000000000..29a63d1f1 --- /dev/null +++ b/src/cpu/minor/fetch1.hh @@ -0,0 +1,381 @@ +/* + * Copyright (c) 2013-2014 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: Andrew Bardsley + */ + +/** + * @file + * + * Fetch1 is responsible for fetching "lines" from memory and passing + * them to Fetch2 + */ + +#ifndef __CPU_MINOR_FETCH1_HH__ +#define __CPU_MINOR_FETCH1_HH__ + +#include "cpu/minor/buffers.hh" +#include "cpu/minor/cpu.hh" +#include "cpu/minor/pipe_data.hh" +#include "cpu/base.hh" +#include "mem/packet.hh" + +namespace Minor +{ + +/** A stage responsible for fetching "lines" from memory and passing + * them to Fetch2 */ +class Fetch1 : public Named +{ + protected: + /** Exposable fetch port */ + class IcachePort : public MinorCPU::MinorCPUPort + { + protected: + /** My owner */ + Fetch1 &fetch; + + public: + IcachePort(std::string name, Fetch1 &fetch_, MinorCPU &cpu) : + MinorCPU::MinorCPUPort(name, cpu), fetch(fetch_) + { } + + protected: + bool recvTimingResp(PacketPtr pkt) + { return fetch.recvTimingResp(pkt); } + + void recvRetry() { fetch.recvRetry(); } + }; + + /** Memory access queuing. + * + * A request can be submitted by pushing it onto the requests queue after + * issuing an ITLB lookup (state becomes InTranslation) with a + * FetchSenderState senderState containing the current lineSeqNum and + * stream/predictionSeqNum. + * + * Translated packets (state becomes Translation) are then passed to the + * memory system and the transfers queue (state becomes RequestIssuing). + * Retries are handled by leaving the packet on the requests queue and + * changing the state to IcacheNeedsRetry). + * + * Responses from the memory system alter the request object (state + * become Complete). Responses can be picked up from the head of the + * transfers queue to pass on to Fetch2. */ + + /** Structure to hold SenderState info through + * translation and memory accesses. */ + class FetchRequest : + public BaseTLB::Translation, /* For TLB lookups */ + public Packet::SenderState /* For packing into a Packet */ + { + protected: + /** Owning fetch unit */ + Fetch1 &fetch; + + public: + /** Progress of this request through address translation and + * memory */ + enum FetchRequestState + { + NotIssued, /* Just been made */ + InTranslation, /* Issued to ITLB, must wait for reqply */ + Translated, /* Translation complete */ + RequestIssuing, /* Issued to memory, must wait for response */ + Complete /* Complete. Either a fault, or a fetched line */ + }; + + FetchRequestState state; + + /** Identity of the line that this request will generate */ + InstId id; + + /** FetchRequests carry packets while they're in the requests and + * transfers responses queues. When a Packet returns from the memory + * system, its request needs to have its packet updated as this may + * have changed in flight */ + PacketPtr packet; + + /** The underlying request that this fetch represents */ + Request request; + + /** PC to fixup with line address */ + TheISA::PCState pc; + + /** Fill in a fault if one happens during fetch, check this by + * picking apart the response packet */ + Fault fault; + + /** Make a packet to use with the memory transaction */ + void makePacket(); + + /** Report interface */ + void reportData(std::ostream &os) const; + + /** Is this line out of date with the current stream/prediction + * sequence and can it be discarded without orphaning in flight + * TLB lookups/memory accesses? */ + bool isDiscardable() const; + + /** Is this a complete read line or fault */ + bool isComplete() const { return state == Complete; } + + protected: + /** BaseTLB::Translation interface */ + + /** Interface for ITLB responses. We can handle delay, so don't + * do anything */ + void markDelayed() { } + + /** Interface for ITLB responses. Populates self and then passes + * the request on to the ports' handleTLBResponse member + * function */ + void finish(Fault fault_, RequestPtr request_, ThreadContext *tc, + BaseTLB::Mode mode); + + public: + FetchRequest(Fetch1 &fetch_, InstId id_, TheISA::PCState pc_) : + SenderState(), + fetch(fetch_), + state(NotIssued), + id(id_), + packet(NULL), + request(), + pc(pc_), + fault(NoFault) + { } + + ~FetchRequest(); + }; + + typedef FetchRequest *FetchRequestPtr; + + protected: + /** Construction-assigned data members */ + + /** Pointer back to the containing CPU */ + MinorCPU &cpu; + + /** Input port carrying branch requests from Execute */ + Latch<BranchData>::Output inp; + /** Output port carrying read lines to Fetch2 */ + Latch<ForwardLineData>::Input out; + /** Input port carrying branch predictions from Fetch2 */ + Latch<BranchData>::Output prediction; + + /** Interface to reserve space in the next stage */ + Reservable &nextStageReserve; + + /** IcachePort to pass to the CPU. Fetch1 is the only module that uses + * it. */ + IcachePort icachePort; + + /** Line snap size in bytes. All fetches clip to make their ends not + * extend beyond this limit. Setting this to the machine L1 cache line + * length will result in fetches never crossing line boundaries. */ + unsigned int lineSnap; + + /** Maximum fetch width in bytes. Setting this (and lineSnap) to the + * machine L1 cache line length will result in fetches of whole cache + * lines. Setting this to sizeof(MachInst) will result it fetches of + * single instructions (except near the end of lineSnap lines) */ + unsigned int maxLineWidth; + + /** Maximum number of fetches allowed in flight (in queues or memory) */ + unsigned int fetchLimit; + + protected: + /** Cycle-by-cycle state */ + + /** State of memory access for head instruction fetch */ + enum FetchState + { + FetchHalted, /* Not fetching, waiting to be woken by transition + to FetchWaitingForPC. The PC is not valid in this state */ + FetchWaitingForPC, /* Not fetching, waiting for stream change. + This doesn't stop issued fetches from being returned and + processed or for branches to change the state to Running. */ + FetchRunning /* Try to fetch, when possible */ + }; + + /** Stage cycle-by-cycle state */ + + FetchState state; + + /** Fetch PC value. This is updated by branches from Execute, branch + * prediction targets from Fetch2 and by incrementing it as we fetch + * lines subsequent to those two sources. */ + TheISA::PCState pc; + + /** Stream sequence number. This changes on request from Execute and is + * used to tag instructions by the fetch stream to which they belong. + * Execute originates new prediction sequence numbers. */ + InstSeqNum streamSeqNum; + + /** Prediction sequence number. This changes when requests from Execute + * or Fetch2 ask for a change of fetch address and is used to tag lines + * by the prediction to which they belong. Fetch2 originates + * prediction sequence numbers. */ + InstSeqNum predictionSeqNum; + + /** The sequence number expected for the next returned cache line. The + * responses queue should be ordered and so, if the front of that queue + * has a lower lineSeqNum than this, lines need to be discarded. If it + * has a higher lineSeqNum, our line hasn't appeared yet */ + InstSeqNum expectedLineSeqNum; + + /** Blocked indication for report */ + bool blocked; + + /** State of memory access for head instruction fetch */ + enum IcacheState + { + IcacheRunning, /* Default. Step icache queues when possible */ + IcacheNeedsRetry /* Request rejected, will be asked to retry */ + }; + + typedef Queue<FetchRequestPtr, + ReportTraitsPtrAdaptor<FetchRequestPtr>, + NoBubbleTraits<FetchRequestPtr> > + FetchQueue; + + /** Queue of address translated requests from Fetch1 */ + FetchQueue requests; + + /** Queue of in-memory system requests and responses */ + FetchQueue transfers; + + /** Retry state of icache_port */ + IcacheState icacheState; + + /** Sequence number for line fetch used for ordering lines to flush */ + InstSeqNum lineSeqNum; + + /** Count of the number fetches which have left the transfers queue + * and are in the 'wild' in the memory system. Try not to rely on + * this value, it's better to code without knowledge of the number + * of outstanding accesses */ + unsigned int numFetchesInMemorySystem; + /** Number of requests inside the ITLB rather than in the queues. + * All requests so located *must* have reserved space in the + * transfers queue */ + unsigned int numFetchesInITLB; + + protected: + friend std::ostream &operator <<(std::ostream &os, + Fetch1::FetchState state); + + /** Start fetching from a new address. */ + void changeStream(const BranchData &branch); + + /** Update streamSeqNum and predictionSeqNum from the given branch (and + * assume these have changed and discard (on delivery) all lines in + * flight) */ + void updateExpectedSeqNums(const BranchData &branch); + + /** Convert a response to a ForwardLineData */ + void processResponse(FetchRequestPtr response, + ForwardLineData &line); + + friend std::ostream &operator <<(std::ostream &os, + IcacheState state); + + /** Insert a line fetch into the requests. This can be a partial + * line request where the given address has a non-0 offset into a + * line. */ + void fetchLine(); + + /** Try and issue a fetch for a translated request at the + * head of the requests queue. Also tries to move the request + * between queues */ + void tryToSendToTransfers(FetchRequestPtr request); + + /** Try to send (or resend) a memory request's next/only packet to + * the memory system. Returns true if the fetch was successfully + * sent to memory */ + bool tryToSend(FetchRequestPtr request); + + /** Move a request between queues */ + void moveFromRequestsToTransfers(FetchRequestPtr request); + + /** Step requests along between requests and transfers queues */ + void stepQueues(); + + /** Pop a request from the given queue and correctly deallocate and + * discard it. */ + void popAndDiscard(FetchQueue &queue); + + /** Handle pushing a TLB response onto the right queue */ + void handleTLBResponse(FetchRequestPtr response); + + /** Returns the total number of queue occupancy, in-ITLB and + * in-memory system fetches */ + unsigned int numInFlightFetches(); + + /** Print the appropriate MinorLine line for a fetch response */ + void minorTraceResponseLine(const std::string &name, + FetchRequestPtr response) const; + + /** Memory interface */ + virtual bool recvTimingResp(PacketPtr pkt); + virtual void recvRetry(); + + public: + Fetch1(const std::string &name_, + MinorCPU &cpu_, + MinorCPUParams ¶ms, + Latch<BranchData>::Output inp_, + Latch<ForwardLineData>::Input out_, + Latch<BranchData>::Output prediction_, + Reservable &next_stage_input_buffer); + + public: + /** Returns the IcachePort owned by this Fetch1 */ + MinorCPU::MinorCPUPort &getIcachePort() { return icachePort; } + + /** Pass on input/buffer data to the output if you can */ + void evaluate(); + + void minorTrace() const; + + /** Is this stage drained? For Fetch1, draining is initiated by + * Execute signalling a branch with the reason HaltFetch */ + bool isDrained(); +}; + +} + +#endif /* __CPU_MINOR_FETCH1_HH__ */ diff --git a/src/cpu/minor/fetch2.cc b/src/cpu/minor/fetch2.cc new file mode 100644 index 000000000..4827b75fc --- /dev/null +++ b/src/cpu/minor/fetch2.cc @@ -0,0 +1,543 @@ +/* + * Copyright (c) 2013-2014 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: Andrew Bardsley + */ + +#include <string> + +#include "arch/decoder.hh" +#include "arch/utility.hh" +#include "cpu/minor/fetch2.hh" +#include "cpu/minor/pipeline.hh" +#include "cpu/pred/bpred_unit.hh" +#include "debug/Branch.hh" +#include "debug/Fetch.hh" +#include "debug/MinorTrace.hh" + +namespace Minor +{ + +Fetch2::Fetch2(const std::string &name, + MinorCPU &cpu_, + MinorCPUParams ¶ms, + Latch<ForwardLineData>::Output inp_, + Latch<BranchData>::Output branchInp_, + Latch<BranchData>::Input predictionOut_, + Latch<ForwardInstData>::Input out_, + Reservable &next_stage_input_buffer) : + Named(name), + cpu(cpu_), + inp(inp_), + branchInp(branchInp_), + predictionOut(predictionOut_), + out(out_), + nextStageReserve(next_stage_input_buffer), + outputWidth(params.decodeInputWidth), + processMoreThanOneInput(params.fetch2CycleInput), + branchPredictor(*params.branchPred), + inputBuffer(name + ".inputBuffer", "lines", params.fetch2InputBufferSize), + inputIndex(0), + pc(TheISA::PCState(0)), + havePC(false), + lastStreamSeqNum(InstId::firstStreamSeqNum), + fetchSeqNum(InstId::firstFetchSeqNum), + expectedStreamSeqNum(InstId::firstStreamSeqNum), + predictionSeqNum(InstId::firstPredictionSeqNum) +{ + if (outputWidth < 1) + fatal("%s: decodeInputWidth must be >= 1 (%d)\n", name, outputWidth); + + if (params.fetch2InputBufferSize < 1) { + fatal("%s: fetch2InputBufferSize must be >= 1 (%d)\n", name, + params.fetch2InputBufferSize); + } +} + +const ForwardLineData * +Fetch2::getInput() +{ + /* Get a line from the inputBuffer to work with */ + if (!inputBuffer.empty()) { + return &(inputBuffer.front()); + } else { + return NULL; + } +} + +void +Fetch2::popInput() +{ + if (!inputBuffer.empty()) { + inputBuffer.front().freeLine(); + inputBuffer.pop(); + } + + inputIndex = 0; +} + +void +Fetch2::dumpAllInput() +{ + DPRINTF(Fetch, "Dumping whole input buffer\n"); + while (!inputBuffer.empty()) + popInput(); + + inputIndex = 0; +} + +void +Fetch2::updateBranchPrediction(const BranchData &branch) +{ + MinorDynInstPtr inst = branch.inst; + + /* Don't even consider instructions we didn't try to predict or faults */ + if (inst->isFault() || !inst->triedToPredict) + return; + + switch (branch.reason) { + case BranchData::NoBranch: + /* No data to update */ + break; + case BranchData::Interrupt: + /* Never try to predict interrupts */ + break; + case BranchData::SuspendThread: + /* Don't need to act on suspends */ + break; + case BranchData::WakeupFetch: + /* Don't need to act on wakeups, no instruction tied to action. */ + break; + case BranchData::HaltFetch: + /* Don't need to act on fetch wakeup */ + break; + case BranchData::BranchPrediction: + /* Shouldn't happen. Fetch2 is the only source of + * BranchPredictions */ + break; + case BranchData::UnpredictedBranch: + /* Unpredicted branch or barrier */ + DPRINTF(Branch, "Unpredicted branch seen inst: %s\n", *inst); + branchPredictor.squash(inst->id.fetchSeqNum, + branch.target, true, inst->id.threadId); + break; + case BranchData::CorrectlyPredictedBranch: + /* Predicted taken, was taken */ + DPRINTF(Branch, "Branch predicted correctly inst: %s\n", *inst); + branchPredictor.update(inst->id.fetchSeqNum, + inst->id.threadId); + break; + case BranchData::BadlyPredictedBranch: + /* Predicted taken, not taken */ + DPRINTF(Branch, "Branch mis-predicted inst: %s\n", *inst); + branchPredictor.squash(inst->id.fetchSeqNum, + branch.target /* Not used */, false, inst->id.threadId); + break; + case BranchData::BadlyPredictedBranchTarget: + /* Predicted taken, was taken but to a different target */ + DPRINTF(Branch, "Branch mis-predicted target inst: %s target: %s\n", + *inst, branch.target); + branchPredictor.squash(inst->id.fetchSeqNum, + branch.target, true, inst->id.threadId); + break; + } +} + +void +Fetch2::predictBranch(MinorDynInstPtr inst, BranchData &branch) +{ + TheISA::PCState inst_pc = inst->pc; + + assert(!inst->predictedTaken); + + /* Skip non-control/sys call instructions */ + if (inst->staticInst->isControl() || + inst->staticInst->isSyscall()) + { + /* Tried to predict */ + inst->triedToPredict = true; + + DPRINTF(Branch, "Trying to predict for inst: %s\n", *inst); + + if (branchPredictor.predict(inst->staticInst, + inst->id.fetchSeqNum, inst_pc, + inst->id.threadId)) + { + inst->predictedTaken = true; + inst->predictedTarget = inst_pc; + branch.target = inst_pc; + } + } else { + DPRINTF(Branch, "Not attempting prediction for inst: %s\n", *inst); + } + + /* If we predict taken, set branch and update sequence numbers */ + if (inst->predictedTaken) { + /* Update the predictionSeqNum and remember the streamSeqNum that it + * was associated with */ + expectedStreamSeqNum = inst->id.streamSeqNum; + + BranchData new_branch = BranchData(BranchData::BranchPrediction, + inst->id.streamSeqNum, predictionSeqNum + 1, + inst->predictedTarget, inst); + + /* Mark with a new prediction number by the stream number of the + * instruction causing the prediction */ + predictionSeqNum++; + branch = new_branch; + + DPRINTF(Branch, "Branch predicted taken inst: %s target: %s" + " new predictionSeqNum: %d\n", + *inst, inst->predictedTarget, predictionSeqNum); + } +} + +void +Fetch2::evaluate() +{ + inputBuffer.setTail(*inp.outputWire); + ForwardInstData &insts_out = *out.inputWire; + BranchData prediction; + BranchData &branch_inp = *branchInp.outputWire; + + assert(insts_out.isBubble()); + + blocked = false; + + /* React to branches from Execute to update local branch prediction + * structures */ + updateBranchPrediction(branch_inp); + + /* If a branch arrives, don't try and do anything about it. Only + * react to your own predictions */ + if (branch_inp.isStreamChange()) { + DPRINTF(Fetch, "Dumping all input as a stream changing branch" + " has arrived\n"); + dumpAllInput(); + havePC = false; + } + + /* Even when blocked, clear out input lines with the wrong + * prediction sequence number */ + { + const ForwardLineData *line_in = getInput(); + + while (line_in && + expectedStreamSeqNum == line_in->id.streamSeqNum && + predictionSeqNum != line_in->id.predictionSeqNum) + { + DPRINTF(Fetch, "Discarding line %s" + " due to predictionSeqNum mismatch (expected: %d)\n", + line_in->id, predictionSeqNum); + + popInput(); + havePC = false; + + if (processMoreThanOneInput) { + DPRINTF(Fetch, "Wrapping\n"); + line_in = getInput(); + } else { + line_in = NULL; + } + } + } + + if (!nextStageReserve.canReserve()) { + blocked = true; + } else { + const ForwardLineData *line_in = getInput(); + + unsigned int output_index = 0; + + /* Pack instructions into the output while we can. This may involve + * using more than one input line. Note that lineWidth will be 0 + * for faulting lines */ + while (line_in && + (line_in->isFault() || + inputIndex < line_in->lineWidth) && /* More input */ + output_index < outputWidth && /* More output to fill */ + prediction.isBubble() /* No predicted branch */) + { + ThreadContext *thread = cpu.getContext(line_in->id.threadId); + TheISA::Decoder *decoder = thread->getDecoderPtr(); + + /* Discard line due to prediction sequence number being wrong but + * without the streamSeqNum number having changed */ + bool discard_line = + expectedStreamSeqNum == line_in->id.streamSeqNum && + predictionSeqNum != line_in->id.predictionSeqNum; + + /* Set the PC if the stream changes. Setting havePC to false in + * a previous cycle handles all other change of flow of control + * issues */ + bool set_pc = lastStreamSeqNum != line_in->id.streamSeqNum; + + if (!discard_line && (!havePC || set_pc)) { + /* Set the inputIndex to be the MachInst-aligned offset + * from lineBaseAddr of the new PC value */ + inputIndex = + (line_in->pc.instAddr() & BaseCPU::PCMask) - + line_in->lineBaseAddr; + DPRINTF(Fetch, "Setting new PC value: %s inputIndex: 0x%x" + " lineBaseAddr: 0x%x lineWidth: 0x%x\n", + line_in->pc, inputIndex, line_in->lineBaseAddr, + line_in->lineWidth); + pc = line_in->pc; + havePC = true; + decoder->reset(); + } + + /* The generated instruction. Leave as NULL if no instruction + * is to be packed into the output */ + MinorDynInstPtr dyn_inst = NULL; + + if (discard_line) { + /* Rest of line was from an older prediction in the same + * stream */ + DPRINTF(Fetch, "Discarding line %s (from inputIndex: %d)" + " due to predictionSeqNum mismatch (expected: %d)\n", + line_in->id, inputIndex, predictionSeqNum); + } else if (line_in->isFault()) { + /* Pack a fault as a MinorDynInst with ->fault set */ + + /* Make a new instruction and pick up the line, stream, + * prediction, thread ids from the incoming line */ + dyn_inst = new MinorDynInst(line_in->id); + + /* Fetch and prediction sequence numbers originate here */ + dyn_inst->id.fetchSeqNum = fetchSeqNum; + dyn_inst->id.predictionSeqNum = predictionSeqNum; + /* To complete the set, test that exec sequence number has + * not been set */ + assert(dyn_inst->id.execSeqNum == 0); + + dyn_inst->pc = pc; + + /* Pack a faulting instruction but allow other + * instructions to be generated. (Fetch2 makes no + * immediate judgement about streamSeqNum) */ + dyn_inst->fault = line_in->fault; + DPRINTF(Fetch, "Fault being passed output_index: " + "%d: %s\n", output_index, dyn_inst->fault->name()); + } else { + uint8_t *line = line_in->line; + + TheISA::MachInst inst_word; + /* The instruction is wholly in the line, can just + * assign */ + inst_word = TheISA::gtoh( + *(reinterpret_cast<TheISA::MachInst *> + (line + inputIndex))); + + if (!decoder->instReady()) { + decoder->moreBytes(pc, + line_in->lineBaseAddr + inputIndex, inst_word); + DPRINTF(Fetch, "Offering MachInst to decoder" + " addr: 0x%x\n", line_in->lineBaseAddr + inputIndex); + } + + /* Maybe make the above a loop to accomodate ISAs with + * instructions longer than sizeof(MachInst) */ + + if (decoder->instReady()) { + /* Make a new instruction and pick up the line, stream, + * prediction, thread ids from the incoming line */ + dyn_inst = new MinorDynInst(line_in->id); + + /* Fetch and prediction sequence numbers originate here */ + dyn_inst->id.fetchSeqNum = fetchSeqNum; + dyn_inst->id.predictionSeqNum = predictionSeqNum; + /* To complete the set, test that exec sequence number + * has not been set */ + assert(dyn_inst->id.execSeqNum == 0); + + /* Note that the decoder can update the given PC. + * Remember not to assign it until *after* calling + * decode */ + StaticInstPtr decoded_inst = decoder->decode(pc); + dyn_inst->staticInst = decoded_inst; + + dyn_inst->pc = pc; + + DPRINTF(Fetch, "Instruction extracted from line %s" + " lineWidth: %d output_index: %d inputIndex: %d" + " pc: %s inst: %s\n", + line_in->id, + line_in->lineWidth, output_index, inputIndex, + pc, *dyn_inst); + +#if THE_ISA == X86_ISA || THE_ISA == ARM_ISA + /* In SE mode, it's possible to branch to a microop when + * replaying faults such as page faults (or simply + * intra-microcode branches in X86). Unfortunately, + * as Minor has micro-op decomposition in a separate + * pipeline stage from instruction decomposition, the + * following advancePC (which may follow a branch with + * microPC() != 0) *must* see a fresh macroop. This + * kludge should be improved with an addition to PCState + * but I offer it in this form for the moment + * + * X86 can branch within microops so we need to deal with + * the case that, after a branch, the first un-advanced PC + * may be pointing to a microop other than 0. Once + * advanced, however, the microop number *must* be 0 */ + pc.upc(0); + pc.nupc(1); +#endif + + /* Advance PC for the next instruction */ + TheISA::advancePC(pc, decoded_inst); + + /* Predict any branches and issue a branch if + * necessary */ + predictBranch(dyn_inst, prediction); + } else { + DPRINTF(Fetch, "Inst not ready yet\n"); + } + + /* Step on the pointer into the line if there's no + * complete instruction waiting */ + if (decoder->needMoreBytes()) { + inputIndex += sizeof(TheISA::MachInst); + + DPRINTF(Fetch, "Updated inputIndex value PC: %s" + " inputIndex: 0x%x lineBaseAddr: 0x%x lineWidth: 0x%x\n", + line_in->pc, inputIndex, line_in->lineBaseAddr, + line_in->lineWidth); + } + } + + if (dyn_inst) { + /* Step to next sequence number */ + fetchSeqNum++; + + /* Correctly size the output before writing */ + if (output_index == 0) + insts_out.resize(outputWidth); + /* Pack the generated dynamic instruction into the output */ + insts_out.insts[output_index] = dyn_inst; + output_index++; + + /* Output MinorTrace instruction info for + * pre-microop decomposition macroops */ + if (DTRACE(MinorTrace) && !dyn_inst->isFault() && + dyn_inst->staticInst->isMacroop()) + { + dyn_inst->minorTraceInst(*this); + } + } + + /* Remember the streamSeqNum of this line so we can tell when + * we change stream */ + lastStreamSeqNum = line_in->id.streamSeqNum; + + /* Asked to discard line or there was a branch or fault */ + if (!prediction.isBubble() || /* The remains of a + line with a prediction in it */ + line_in->isFault() /* A line which is just a fault */) + { + DPRINTF(Fetch, "Discarding all input on branch/fault\n"); + dumpAllInput(); + havePC = false; + line_in = NULL; + } else if (discard_line) { + /* Just discard one line, one's behind it may have new + * stream sequence numbers. There's a DPRINTF above + * for this event */ + popInput(); + havePC = false; + line_in = NULL; + } else if (inputIndex == line_in->lineWidth) { + /* Got to end of a line, pop the line but keep PC + * in case this is a line-wrapping inst. */ + popInput(); + line_in = NULL; + } + + if (!line_in && processMoreThanOneInput) { + DPRINTF(Fetch, "Wrapping\n"); + line_in = getInput(); + } + } + + /* The rest of the output (if any) should already have been packed + * with bubble instructions by insts_out's initialisation */ + } + + /** Reserve a slot in the next stage and output data */ + *predictionOut.inputWire = prediction; + + /* If we generated output, reserve space for the result in the next stage + * and mark the stage as being active this cycle */ + if (!insts_out.isBubble()) { + /* Note activity of following buffer */ + cpu.activityRecorder->activity(); + nextStageReserve.reserve(); + } + + /* If we still have input to process and somewhere to put it, + * mark stage as active */ + if (getInput() && nextStageReserve.canReserve()) + cpu.activityRecorder->activateStage(Pipeline::Fetch2StageId); + + /* Make sure the input (if any left) is pushed */ + inputBuffer.pushTail(); +} + +bool +Fetch2::isDrained() +{ + return inputBuffer.empty() && + (*inp.outputWire).isBubble() && + (*predictionOut.inputWire).isBubble(); +} + +void +Fetch2::minorTrace() const +{ + std::ostringstream data; + + if (blocked) + data << 'B'; + else + (*out.inputWire).reportData(data); + + MINORTRACE("inputIndex=%d havePC=%d predictionSeqNum=%d insts=%s\n", + inputIndex, havePC, predictionSeqNum, data.str()); + inputBuffer.minorTrace(); +} + +} diff --git a/src/cpu/minor/fetch2.hh b/src/cpu/minor/fetch2.hh new file mode 100644 index 000000000..2fc38b377 --- /dev/null +++ b/src/cpu/minor/fetch2.hh @@ -0,0 +1,184 @@ +/* + * Copyright (c) 2013-2014 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: Andrew Bardsley + */ + +/** + * @file + * + * Fetch2 receives lines of data from Fetch1, separates them into + * instructions and passes them to Decode + */ + +#ifndef __CPU_MINOR_FETCH2_HH__ +#define __CPU_MINOR_FETCH2_HH__ + +#include "cpu/minor/buffers.hh" +#include "cpu/minor/cpu.hh" +#include "cpu/minor/pipe_data.hh" +#include "cpu/pred/bpred_unit.hh" +#include "params/MinorCPU.hh" + +namespace Minor +{ + +/** This stage receives lines of data from Fetch1, separates them into + * instructions and passes them to Decode */ +class Fetch2 : public Named +{ + protected: + /** Pointer back to the containing CPU */ + MinorCPU &cpu; + + /** Input port carrying lines from Fetch1 */ + Latch<ForwardLineData>::Output inp; + + /** Input port carrying branches from Execute. This is a snoop of the + * data provided to F1. */ + Latch<BranchData>::Output branchInp; + + /** Output port carrying predictions back to Fetch1 */ + Latch<BranchData>::Input predictionOut; + + /** Output port carrying instructions into Decode */ + Latch<ForwardInstData>::Input out; + + /** Interface to reserve space in the next stage */ + Reservable &nextStageReserve; + + /** Width of output of this stage/input of next in instructions */ + unsigned int outputWidth; + + /** If true, more than one input word can be processed each cycle if + * there is room in the output to contain its processed data */ + bool processMoreThanOneInput; + + /** Branch predictor passed from Python configuration */ + BPredUnit &branchPredictor; + + public: + /* Public so that Pipeline can pass it to Fetch1 */ + InputBuffer<ForwardLineData> inputBuffer; + + protected: + /** Data members after this line are cycle-to-cycle state */ + + /** Index into an incompletely processed input line that instructions + * are to be extracted from */ + unsigned int inputIndex; + + /** Remembered program counter value. Between contiguous lines, this + * is just updated with advancePC. For lines following changes of + * stream, a new PC must be loaded and havePC be set. + * havePC is needed to accomodate instructions which span across + * lines meaning that Fetch2 and the decoder need to remember a PC + * value and a partially-offered instruction from the previous line */ + TheISA::PCState pc; + + /** PC is currently valid. Initially false, gets set to true when a + * change-of-stream line is received and false again when lines are + * discarded for any reason */ + bool havePC; + + /** Stream sequence number of the last seen line used to identify changes + * of instruction stream */ + InstSeqNum lastStreamSeqNum; + + /** Fetch2 is the source of fetch sequence numbers. These represent the + * sequence that instructions were extracted from fetched lines. */ + InstSeqNum fetchSeqNum; + + /** Stream sequence number remembered from last time the predictionSeqNum + * changed. Lines should only be discarded when their predictionSeqNums + * disagree with Fetch2::predictionSeqNum *and* they are from the same + * stream that bore that prediction number */ + InstSeqNum expectedStreamSeqNum; + + /** Fetch2 is the source of prediction sequence numbers. These represent + * predicted changes of control flow sources from branch prediction in + * Fetch2. */ + InstSeqNum predictionSeqNum; + + /** Blocked indication for report */ + bool blocked; + + protected: + /** Get a piece of data to work on from the inputBuffer, or 0 if there + * is no data. */ + const ForwardLineData *getInput(); + + /** Pop an element off the input buffer, if there are any */ + void popInput(); + + /** Dump the whole contents of the input buffer. Useful after a + * prediction changes control flow */ + void dumpAllInput(); + + /** Update local branch prediction structures from feedback from + * Execute. */ + void updateBranchPrediction(const BranchData &branch); + + /** Predicts branches for the given instruction. Updates the + * instruction's predicted... fields and also the branch which + * carries the prediction to Fetch1 */ + void predictBranch(MinorDynInstPtr inst, BranchData &branch); + + public: + Fetch2(const std::string &name, + MinorCPU &cpu_, + MinorCPUParams ¶ms, + Latch<ForwardLineData>::Output inp_, + Latch<BranchData>::Output branchInp_, + Latch<BranchData>::Input predictionOut_, + Latch<ForwardInstData>::Input out_, + Reservable &next_stage_input_buffer); + + public: + /** Pass on input/buffer data to the output if you can */ + void evaluate(); + + void minorTrace() const; + + /** Is this stage drained? For Fetch2, draining is initiated by + * Execute halting Fetch1 causing Fetch2 to naturally drain. + * Branch predictions are ignored by Fetch1 during halt */ + bool isDrained(); +}; + +} + +#endif /* __CPU_MINOR_FETCH2_HH__ */ diff --git a/src/cpu/minor/func_unit.cc b/src/cpu/minor/func_unit.cc new file mode 100644 index 000000000..1a75c4aa8 --- /dev/null +++ b/src/cpu/minor/func_unit.cc @@ -0,0 +1,242 @@ +/* + * Copyright (c) 2013-2014 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: Andrew Bardsley + */ + +#include <iomanip> +#include <sstream> +#include <typeinfo> + +#include "cpu/minor/func_unit.hh" +#include "debug/MinorTiming.hh" +#include "enums/OpClass.hh" + +MinorOpClass * +MinorOpClassParams::create() +{ + return new MinorOpClass(this); +} + +MinorOpClassSet * +MinorOpClassSetParams::create() +{ + return new MinorOpClassSet(this); +} + +MinorFUTiming * +MinorFUTimingParams::create() +{ + return new MinorFUTiming(this); +} + +MinorFU * +MinorFUParams::create() +{ + return new MinorFU(this); +} + +MinorFUPool * +MinorFUPoolParams::create() +{ + return new MinorFUPool(this); +} + +MinorOpClassSet::MinorOpClassSet(const MinorOpClassSetParams *params) : + SimObject(params), + opClasses(params->opClasses), + /* Initialise to true for an empty list so that 'fully capable' is + * the default */ + capabilityList(Num_OpClasses, (opClasses.empty() ? true : false)) +{ + for (unsigned int i = 0; i < opClasses.size(); i++) + capabilityList[opClasses[i]->opClass] = true; +} + +MinorFUTiming::MinorFUTiming( + const MinorFUTimingParams *params) : + SimObject(params), + mask(params->mask), + match(params->match), + description(params->description), + suppress(params->suppress), + extraCommitLat(params->extraCommitLat), + extraCommitLatExpr(params->extraCommitLatExpr), + extraAssumedLat(params->extraAssumedLat), + srcRegsRelativeLats(params->srcRegsRelativeLats), + opClasses(params->opClasses) +{ } + +namespace Minor +{ + +void +QueuedInst::reportData(std::ostream &os) const +{ + inst->reportData(os); +} + +FUPipeline::FUPipeline(const std::string &name, const MinorFU &description_, + ClockedObject &timeSource_) : + FUPipelineBase(name, "insts", description_.opLat), + description(description_), + timeSource(timeSource_), + nextInsertCycle(Cycles(0)) +{ + /* Issue latencies are set to 1 in calls to addCapability here. + * Issue latencies are associated with the pipeline as a whole, + * rather than instruction classes in Minor */ + + /* All pipelines should be able to execute No_OpClass instructions */ + addCapability(No_OpClass, description.opLat, 1); + + /* Add the capabilities listed in the MinorFU for this functional unit */ + for (unsigned int i = 0; i < description.opClasses->opClasses.size(); + i++) + { + addCapability(description.opClasses->opClasses[i]->opClass, + description.opLat, 1); + } + + for (unsigned int i = 0; i < description.timings.size(); i++) { + MinorFUTiming &timing = *(description.timings[i]); + + if (DTRACE(MinorTiming)) { + std::ostringstream lats; + + unsigned int num_lats = timing.srcRegsRelativeLats.size(); + unsigned int j = 0; + while (j < num_lats) { + lats << timing.srcRegsRelativeLats[j]; + + j++; + if (j != num_lats) + lats << ','; + } + + DPRINTFS(MinorTiming, static_cast<Named *>(this), + "Adding extra timing decode pattern %d to FU" + " mask: %016x match: %016x srcRegLatencies: %s\n", + i, timing.mask, timing.match, lats.str()); + } + } + + const std::vector<unsigned> &cant_forward = + description.cantForwardFromFUIndices; + + /* Setup the bit vector cantForward... with the set indices + * specified in the parameters */ + for (auto i = cant_forward.begin(); i != cant_forward.end(); ++i) { + cantForwardFromFUIndices.resize((*i) + 1, false); + cantForwardFromFUIndices[*i] = true; + } +} + +Cycles +FUPipeline::cyclesBeforeInsert() +{ + if (nextInsertCycle == 0 || timeSource.curCycle() > nextInsertCycle) + return Cycles(0); + else + return nextInsertCycle - timeSource.curCycle(); +} + +bool +FUPipeline::canInsert() const +{ + return nextInsertCycle == 0 || timeSource.curCycle() >= nextInsertCycle; +} + +void +FUPipeline::advance() +{ + bool was_stalled = stalled; + + /* If an instruction was pushed into the pipeline, set the delay before + * the next instruction can follow */ + if (alreadyPushed()) { + if (nextInsertCycle <= timeSource.curCycle()) { + nextInsertCycle = timeSource.curCycle() + description.issueLat; + } + } else if (was_stalled && nextInsertCycle != 0) { + /* Don't count stalled cycles as part of the issue latency */ + ++nextInsertCycle; + } + FUPipelineBase::advance(); +} + +MinorFUTiming * +FUPipeline::findTiming(StaticInstPtr inst) +{ +#if THE_ISA == ARM_ISA + /* This should work for any ISA with a POD mach_inst */ + TheISA::ExtMachInst mach_inst = inst->machInst; +#else + /* Just allow extra decode based on op classes */ + uint64_t mach_inst = 0; +#endif + + const std::vector<MinorFUTiming *> &timings = + description.timings; + unsigned int num_timings = timings.size(); + + for (unsigned int i = 0; i < num_timings; i++) { + MinorFUTiming &timing = *timings[i]; + + if (timing.provides(inst->opClass()) && + (mach_inst & timing.mask) == timing.match) + { + DPRINTFS(MinorTiming, static_cast<Named *>(this), + "Found extra timing match (pattern %d '%s')" + " %s %16x (type %s)\n", + i, timing.description, inst->disassemble(0), mach_inst, + typeid(*inst).name()); + + return &timing; + } + } + + if (num_timings != 0) { + DPRINTFS(MinorTiming, static_cast<Named *>(this), + "No extra timing info. found for inst: %s" + " mach_inst: %16x\n", + inst->disassemble(0), mach_inst); + } + + return NULL; +} + +} diff --git a/src/cpu/minor/func_unit.hh b/src/cpu/minor/func_unit.hh new file mode 100644 index 000000000..34da579b6 --- /dev/null +++ b/src/cpu/minor/func_unit.hh @@ -0,0 +1,268 @@ +/* + * Copyright (c) 2013-2014 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: Andrew Bardsley + */ + +/** + * @file + * + * Execute function unit descriptions and pipeline implementations. + */ + +#ifndef __CPU_MINOR_FUNC_UNIT_HH__ +#define __CPU_MINOR_FUNC_UNIT_HH__ + +#include "cpu/minor/buffers.hh" +#include "cpu/minor/dyn_inst.hh" +#include "cpu/func_unit.hh" +#include "cpu/timing_expr.hh" +#include "params/MinorFU.hh" +#include "params/MinorFUPool.hh" +#include "params/MinorOpClass.hh" +#include "params/MinorOpClassSet.hh" +#include "sim/clocked_object.hh" + +/** Boxing for MinorOpClass to get around a build problem with C++11 but + * also allow for future additions to op class checking */ +class MinorOpClass : public SimObject +{ + public: + OpClass opClass; + + public: + MinorOpClass(const MinorOpClassParams *params) : + SimObject(params), + opClass(params->opClass) + { } +}; + +/** Wrapper for a matchable set of op classes */ +class MinorOpClassSet : public SimObject +{ + public: + std::vector<MinorOpClass *> opClasses; + + /** Convenience packing of opClasses into a bit vector for easier + * testing */ + std::vector<bool> capabilityList; + + public: + MinorOpClassSet(const MinorOpClassSetParams *params); + + public: + /** Does this set support the given op class */ + bool provides(OpClass op_class) { return capabilityList[op_class]; } +}; + +/** Extra timing capability to allow individual ops to have their source + * register dependency latencies tweaked based on the ExtMachInst of the + * source instruction. + */ +class MinorFUTiming: public SimObject +{ + public: + /** Mask off the ExtMachInst of an instruction before comparing with + * match */ + uint64_t mask; + uint64_t match; + + /** Textual description of the decode's purpose */ + std::string description; + + /** If true, instructions matching this mask/match should *not* be + * issued in this FU */ + bool suppress; + + /** Extra latency that the instruction should spend at the end of + * the pipeline */ + Cycles extraCommitLat; + TimingExpr *extraCommitLatExpr; + + /** Extra delay that results should show in the scoreboard after + * leaving the pipeline. If set to Cycles(0) for memory references, + * an 'unpredictable' return time will be set in the scoreboard + * blocking following dependent instructions from issuing */ + Cycles extraAssumedLat; + + /** Cycle offsets from the scoreboard delivery times of register values + * for each of this instruction's source registers (in srcRegs order). + * The offsets are subtracted from the scoreboard returnCycle times. + * For example, for an instruction type with 3 source registers, + * [2, 1, 2] will allow the instruction to issue upto 2 cycles early + * for dependencies on the 1st and 3rd register and upto 1 cycle early + * on the 2nd. */ + std::vector<Cycles> srcRegsRelativeLats; + + /** Extra opClasses check (after the FU one) */ + MinorOpClassSet *opClasses; + + public: + MinorFUTiming(const MinorFUTimingParams *params); + + public: + /** Does the extra decode in this object support the given op class */ + bool provides(OpClass op_class) { return opClasses->provides(op_class); } +}; + +/** A functional unit that can execute any of opClasses operations with a + * single op(eration)Lat(ency) and issueLat(ency) associated with the unit + * rather than each operation (as in src/FuncUnit). + * + * This is very similar to cpu/func_unit but replicated here to allow + * the Minor functional units to change without having to disturb the common + * definition. + */ +class MinorFU : public SimObject +{ + public: + MinorOpClassSet *opClasses; + + /** Delay from issuing the operation, to it reaching the + * end of the associated pipeline */ + Cycles opLat; + + /** Delay after issuing an operation before the next + * operation can be issued */ + Cycles issueLat; + + /** FUs which this pipeline can't receive a forwarded (i.e. relative + * latency != 0) result from */ + std::vector<unsigned int> cantForwardFromFUIndices; + + /** Extra timing info to give timings to individual ops */ + std::vector<MinorFUTiming *> timings; + + public: + MinorFU(const MinorFUParams *params) : + SimObject(params), + opClasses(params->opClasses), + opLat(params->opLat), + issueLat(params->issueLat), + cantForwardFromFUIndices(params->cantForwardFromFUIndices), + timings(params->timings) + { } +}; + +/** A collection of MinorFUs */ +class MinorFUPool : public SimObject +{ + public: + std::vector<MinorFU *> funcUnits; + + public: + MinorFUPool(const MinorFUPoolParams *params) : + SimObject(params), + funcUnits(params->funcUnits) + { } +}; + +namespace Minor +{ + +/** Container class to box instructions in the FUs to make those + * queues have correct bubble behaviour when stepped */ +class QueuedInst +{ + public: + MinorDynInstPtr inst; + + public: + QueuedInst(MinorDynInstPtr inst_ = MinorDynInst::bubble()) : + inst(inst_) + { } + + public: + /** Report and bubble interfaces */ + void reportData(std::ostream &os) const; + bool isBubble() const { return inst->isBubble(); } + + static QueuedInst bubble() + { return QueuedInst(MinorDynInst::bubble()); } +}; + +/** Functional units have pipelines which stall when an inst gets to + * their ends allowing Execute::commit to pick up timing-completed insts + * when it feels like it */ +typedef SelfStallingPipeline<QueuedInst, + ReportTraitsAdaptor<QueuedInst> > FUPipelineBase; + +/** A functional unit configured from a MinorFU object */ +class FUPipeline : public FUPipelineBase, public FuncUnit +{ + public: + /** Functional unit description that this pipeline implements */ + const MinorFU &description; + + /** An FUPipeline needs access to curCycle, use this timing source */ + ClockedObject &timeSource; + + /** Set of operation classes supported by this FU */ + std::bitset<Num_OpClasses> capabilityList; + + /** FUs which this pipeline can't receive a forwarded (i.e. relative + * latency != 0) result from */ + std::vector<bool> cantForwardFromFUIndices; + + public: + /** When can a new instruction be inserted into the pipeline? This is + * an absolute cycle time unless it is 0 in which case the an + * instruction can be pushed straightaway */ + Cycles nextInsertCycle; + + public: + FUPipeline(const std::string &name, const MinorFU &description_, + ClockedObject &timeSource_); + + public: + /** How many cycles must from curCycle before insertion into the + * pipeline is allowed */ + Cycles cyclesBeforeInsert(); + + /** Can an instruction be inserted now? */ + bool canInsert() const; + + /** Find the extra timing information for this instruction. Returns + * NULL if no decode info. is found */ + MinorFUTiming *findTiming(StaticInstPtr inst); + + /** Step the pipeline. Allow multiple steps? */ + void advance(); +}; + +} + +#endif /* __CPU_MINOR_FUNC_UNIT_HH__ */ diff --git a/src/cpu/minor/lsq.cc b/src/cpu/minor/lsq.cc new file mode 100644 index 000000000..c5e38c78d --- /dev/null +++ b/src/cpu/minor/lsq.cc @@ -0,0 +1,1614 @@ +/* + * Copyright (c) 2013-2014 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: Andrew Bardsley + */ + +#include <iomanip> +#include <sstream> + +#include "arch/locked_mem.hh" +#include "arch/mmapped_ipr.hh" +#include "cpu/minor/cpu.hh" +#include "cpu/minor/exec_context.hh" +#include "cpu/minor/execute.hh" +#include "cpu/minor/lsq.hh" +#include "cpu/minor/pipeline.hh" +#include "debug/Activity.hh" +#include "debug/MinorMem.hh" + +namespace Minor +{ + +/** Returns the offset of addr into an aligned a block of size block_size */ +static Addr +addrBlockOffset(Addr addr, unsigned int block_size) +{ + return addr & (block_size - 1); +} + +/** Returns true if the given [addr .. addr+size-1] transfer needs to be + * fragmented across a block size of block_size */ +static bool +transferNeedsBurst(Addr addr, unsigned int size, unsigned int block_size) +{ + return (addrBlockOffset(addr, block_size) + size) > block_size; +} + +LSQ::LSQRequest::LSQRequest(LSQ &port_, MinorDynInstPtr inst_, bool isLoad_, + PacketDataPtr data_, uint64_t *res_) : + SenderState(), + port(port_), + inst(inst_), + isLoad(isLoad_), + data(data_), + packet(NULL), + request(), + fault(NoFault), + res(res_), + skipped(false), + issuedToMemory(false), + state(NotIssued) +{ } + +LSQ::AddrRangeCoverage +LSQ::LSQRequest::containsAddrRangeOf( + Addr req1_addr, unsigned int req1_size, + Addr req2_addr, unsigned int req2_size) +{ + /* 'end' here means the address of the byte just past the request + * blocks */ + Addr req2_end_addr = req2_addr + req2_size; + Addr req1_end_addr = req1_addr + req1_size; + + AddrRangeCoverage ret; + + if (req1_addr > req2_end_addr || req1_end_addr < req2_addr) + ret = NoAddrRangeCoverage; + else if (req1_addr <= req2_addr && req1_end_addr >= req2_end_addr) + ret = FullAddrRangeCoverage; + else + ret = PartialAddrRangeCoverage; + + return ret; +} + +LSQ::AddrRangeCoverage +LSQ::LSQRequest::containsAddrRangeOf(LSQRequestPtr other_request) +{ + return containsAddrRangeOf(request.getPaddr(), request.getSize(), + other_request->request.getPaddr(), other_request->request.getSize()); +} + +bool +LSQ::LSQRequest::isBarrier() +{ + return inst->isInst() && inst->staticInst->isMemBarrier(); +} + +bool +LSQ::LSQRequest::needsToBeSentToStoreBuffer() +{ + return state == StoreToStoreBuffer; +} + +void +LSQ::LSQRequest::setState(LSQRequestState new_state) +{ + DPRINTFS(MinorMem, (&port), "Setting state from %d to %d for request:" + " %s\n", state, new_state, *inst); + state = new_state; +} + +bool +LSQ::LSQRequest::isComplete() const +{ + /* @todo, There is currently only one 'completed' state. This + * may not be a good choice */ + return state == Complete; +} + +void +LSQ::LSQRequest::reportData(std::ostream &os) const +{ + os << (isLoad ? 'R' : 'W') << ';'; + inst->reportData(os); + os << ';' << state; +} + +std::ostream & +operator <<(std::ostream &os, LSQ::AddrRangeCoverage coverage) +{ + switch (coverage) { + case LSQ::PartialAddrRangeCoverage: + os << "PartialAddrRangeCoverage"; + break; + case LSQ::FullAddrRangeCoverage: + os << "FullAddrRangeCoverage"; + break; + case LSQ::NoAddrRangeCoverage: + os << "NoAddrRangeCoverage"; + break; + default: + os << "AddrRangeCoverage-" << static_cast<int>(coverage); + break; + } + return os; +} + +std::ostream & +operator <<(std::ostream &os, LSQ::LSQRequest::LSQRequestState state) +{ + switch (state) { + case LSQ::LSQRequest::NotIssued: + os << "NotIssued"; + break; + case LSQ::LSQRequest::InTranslation: + os << "InTranslation"; + break; + case LSQ::LSQRequest::Translated: + os << "Translated"; + break; + case LSQ::LSQRequest::Failed: + os << "Failed"; + break; + case LSQ::LSQRequest::RequestIssuing: + os << "RequestIssuing"; + break; + case LSQ::LSQRequest::StoreToStoreBuffer: + os << "StoreToStoreBuffer"; + break; + case LSQ::LSQRequest::StoreInStoreBuffer: + os << "StoreInStoreBuffer"; + break; + case LSQ::LSQRequest::StoreBufferIssuing: + os << "StoreBufferIssuing"; + break; + case LSQ::LSQRequest::RequestNeedsRetry: + os << "RequestNeedsRetry"; + break; + case LSQ::LSQRequest::StoreBufferNeedsRetry: + os << "StoreBufferNeedsRetry"; + break; + case LSQ::LSQRequest::Complete: + os << "Complete"; + break; + default: + os << "LSQRequestState-" << static_cast<int>(state); + break; + } + return os; +} + +void +LSQ::clearMemBarrier(MinorDynInstPtr inst) +{ + bool is_last_barrier = inst->id.execSeqNum >= lastMemBarrier; + + DPRINTF(MinorMem, "Moving %s barrier out of store buffer inst: %s\n", + (is_last_barrier ? "last" : "a"), *inst); + + if (is_last_barrier) + lastMemBarrier = 0; +} + +void +LSQ::SingleDataRequest::finish(Fault fault_, RequestPtr request_, + ThreadContext *tc, BaseTLB::Mode mode) +{ + fault = fault_; + + port.numAccessesInDTLB--; + + DPRINTFS(MinorMem, (&port), "Received translation response for" + " request: %s\n", *inst); + + makePacket(); + + setState(Translated); + port.tryToSendToTransfers(this); + + /* Let's try and wake up the processor for the next cycle */ + port.cpu.wakeupOnEvent(Pipeline::ExecuteStageId); +} + +void +LSQ::SingleDataRequest::startAddrTranslation() +{ + ThreadContext *thread = port.cpu.getContext( + inst->id.threadId); + + port.numAccessesInDTLB++; + + setState(LSQ::LSQRequest::InTranslation); + + DPRINTFS(MinorMem, (&port), "Submitting DTLB request\n"); + /* Submit the translation request. The response will come through + * finish/markDelayed on the LSQRequest as it bears the Translation + * interface */ + thread->getDTBPtr()->translateTiming( + &request, thread, this, (isLoad ? BaseTLB::Read : BaseTLB::Write)); +} + +void +LSQ::SingleDataRequest::retireResponse(PacketPtr packet_) +{ + DPRINTFS(MinorMem, (&port), "Retiring packet\n"); + packet = packet_; + packetInFlight = false; + setState(Complete); +} + +void +LSQ::SplitDataRequest::finish(Fault fault_, RequestPtr request_, + ThreadContext *tc, BaseTLB::Mode mode) +{ + fault = fault_; + + port.numAccessesInDTLB--; + + unsigned int M5_VAR_USED expected_fragment_index = + numTranslatedFragments; + + numInTranslationFragments--; + numTranslatedFragments++; + + DPRINTFS(MinorMem, (&port), "Received translation response for fragment" + " %d of request: %s\n", expected_fragment_index, *inst); + + assert(request_ == fragmentRequests[expected_fragment_index]); + + /* Wake up next cycle to get things going again in case the + * tryToSendToTransfers does take */ + port.cpu.wakeupOnEvent(Pipeline::ExecuteStageId); + + if (fault != NoFault) { + /* tryToSendToTransfers will handle the fault */ + + DPRINTFS(MinorMem, (&port), "Faulting translation for fragment:" + " %d of request: %s\n", + expected_fragment_index, *inst); + + setState(Translated); + port.tryToSendToTransfers(this); + } else if (numTranslatedFragments == numFragments) { + makeFragmentPackets(); + + setState(Translated); + port.tryToSendToTransfers(this); + } else { + /* Avoid calling translateTiming from within ::finish */ + assert(!translationEvent.scheduled()); + port.cpu.schedule(translationEvent, curTick()); + } +} + +LSQ::SplitDataRequest::SplitDataRequest(LSQ &port_, MinorDynInstPtr inst_, + bool isLoad_, PacketDataPtr data_, uint64_t *res_) : + LSQRequest(port_, inst_, isLoad_, data_, res_), + translationEvent(*this), + numFragments(0), + numInTranslationFragments(0), + numTranslatedFragments(0), + numIssuedFragments(0), + numRetiredFragments(0), + fragmentRequests(), + fragmentPackets() +{ + /* Don't know how many elements are needed until the request is + * populated by the caller. */ +} + +LSQ::SplitDataRequest::~SplitDataRequest() +{ + for (auto i = fragmentRequests.begin(); + i != fragmentRequests.end(); i++) + { + delete *i; + } + + for (auto i = fragmentPackets.begin(); + i != fragmentPackets.end(); i++) + { + delete *i; + } +} + +void +LSQ::SplitDataRequest::makeFragmentRequests() +{ + Addr base_addr = request.getVaddr(); + unsigned int whole_size = request.getSize(); + unsigned int line_width = port.lineWidth; + + unsigned int fragment_size; + Addr fragment_addr; + + /* Assume that this transfer is across potentially many block snap + * boundaries: + * + * | _|________|________|________|___ | + * | |0| 1 | 2 | 3 | 4 | | + * | |_|________|________|________|___| | + * | | | | | | + * + * The first transfer (0) can be up to lineWidth in size. + * All the middle transfers (1-3) are lineWidth in size + * The last transfer (4) can be from zero to lineWidth - 1 in size + */ + unsigned int first_fragment_offset = + addrBlockOffset(base_addr, line_width); + unsigned int last_fragment_size = + addrBlockOffset(base_addr + whole_size, line_width); + unsigned int first_fragment_size = + line_width - first_fragment_offset; + + unsigned int middle_fragments_total_size = + whole_size - (first_fragment_size + last_fragment_size); + + assert(addrBlockOffset(middle_fragments_total_size, line_width) == 0); + + unsigned int middle_fragment_count = + middle_fragments_total_size / line_width; + + numFragments = 1 /* first */ + middle_fragment_count + + (last_fragment_size == 0 ? 0 : 1); + + DPRINTFS(MinorMem, (&port), "Dividing transfer into %d fragmentRequests." + " First fragment size: %d Last fragment size: %d\n", + numFragments, first_fragment_size, + (last_fragment_size == 0 ? line_width : last_fragment_size)); + + assert(((middle_fragment_count * line_width) + + first_fragment_size + last_fragment_size) == whole_size); + + fragment_addr = base_addr; + fragment_size = first_fragment_size; + + /* Just past the last address in the request */ + Addr end_addr = base_addr + whole_size; + + for (unsigned int fragment_index = 0; fragment_index < numFragments; + fragment_index++) + { + bool M5_VAR_USED is_last_fragment = false; + + if (fragment_addr == base_addr) { + /* First fragment */ + fragment_size = first_fragment_size; + } else { + if ((fragment_addr + line_width) > end_addr) { + /* Adjust size of last fragment */ + fragment_size = end_addr - fragment_addr; + is_last_fragment = true; + } else { + /* Middle fragments */ + fragment_size = line_width; + } + } + + Request *fragment = new Request(); + + fragment->setThreadContext(request.contextId(), /* thread id */ 0); + fragment->setVirt(0 /* asid */, + fragment_addr, fragment_size, request.getFlags(), + request.masterId(), + request.getPC()); + + DPRINTFS(MinorMem, (&port), "Generating fragment addr: 0x%x size: %d" + " (whole request addr: 0x%x size: %d) %s\n", + fragment_addr, fragment_size, base_addr, whole_size, + (is_last_fragment ? "last fragment" : "")); + + fragment_addr += fragment_size; + + fragmentRequests.push_back(fragment); + } +} + +void +LSQ::SplitDataRequest::makeFragmentPackets() +{ + Addr base_addr = request.getVaddr(); + + DPRINTFS(MinorMem, (&port), "Making packets for request: %s\n", *inst); + + for (unsigned int fragment_index = 0; fragment_index < numFragments; + fragment_index++) + { + Request *fragment = fragmentRequests[fragment_index]; + + DPRINTFS(MinorMem, (&port), "Making packet %d for request: %s" + " (%d, 0x%x)\n", + fragment_index, *inst, + (fragment->hasPaddr() ? "has paddr" : "no paddr"), + (fragment->hasPaddr() ? fragment->getPaddr() : 0)); + + Addr fragment_addr = fragment->getVaddr(); + unsigned int fragment_size = fragment->getSize(); + + uint8_t *request_data = NULL; + + if (!isLoad) { + /* Split data for Packets. Will become the property of the + * outgoing Packets */ + request_data = new uint8_t[fragment_size]; + std::memcpy(request_data, data + (fragment_addr - base_addr), + fragment_size); + } + + assert(fragment->hasPaddr()); + + PacketPtr fragment_packet = + makePacketForRequest(*fragment, isLoad, this, request_data); + + fragmentPackets.push_back(fragment_packet); + } + + /* Might as well make the overall/response packet here */ + /* Get the physical address for the whole request/packet from the first + * fragment */ + request.setPaddr(fragmentRequests[0]->getPaddr()); + makePacket(); +} + +void +LSQ::SplitDataRequest::startAddrTranslation() +{ + setState(LSQ::LSQRequest::InTranslation); + + makeFragmentRequests(); + + numInTranslationFragments = 0; + numTranslatedFragments = 0; + + /* @todo, just do these in sequence for now with + * a loop of: + * do { + * sendNextFragmentToTranslation ; translateTiming ; finish + * } while (numTranslatedFragments != numFragments); + */ + + /* Do first translation */ + sendNextFragmentToTranslation(); +} + +PacketPtr +LSQ::SplitDataRequest::getHeadPacket() +{ + assert(numIssuedFragments < numFragments); + + return fragmentPackets[numIssuedFragments]; +} + +void +LSQ::SplitDataRequest::stepToNextPacket() +{ + assert(numIssuedFragments < numFragments); + + numIssuedFragments++; +} + +void +LSQ::SplitDataRequest::retireResponse(PacketPtr response) +{ + assert(numRetiredFragments < numFragments); + + DPRINTFS(MinorMem, (&port), "Retiring fragment addr: 0x%x size: %d" + " offset: 0x%x (retired fragment num: %d) %s\n", + response->req->getVaddr(), response->req->getSize(), + request.getVaddr() - response->req->getVaddr(), + numRetiredFragments, + (fault == NoFault ? "" : fault->name())); + + numRetiredFragments++; + + if (skipped) { + /* Skip because we already knew the request had faulted or been + * skipped */ + DPRINTFS(MinorMem, (&port), "Skipping this fragment\n"); + } else if (response->isError()) { + /* Mark up the error and leave to execute to handle it */ + DPRINTFS(MinorMem, (&port), "Fragment has an error, skipping\n"); + setSkipped(); + packet->copyError(response); + } else { + if (isLoad) { + if (!data) { + /* For a split transfer, a Packet must be constructed + * to contain all returning data. This is that packet's + * data */ + data = new uint8_t[request.getSize()]; + } + + /* Populate the portion of the overall response data represented + * by the response fragment */ + std::memcpy( + data + (response->req->getVaddr() - request.getVaddr()), + response->getPtr<uint8_t>(), + response->req->getSize()); + } + } + + /* Complete early if we're skipping are no more in-flight accesses */ + if (skipped && !hasPacketsInMemSystem()) { + DPRINTFS(MinorMem, (&port), "Completed skipped burst\n"); + setState(Complete); + if (packet->needsResponse()) + packet->makeResponse(); + } + + if (numRetiredFragments == numFragments) + setState(Complete); + + if (!skipped && isComplete()) { + DPRINTFS(MinorMem, (&port), "Completed burst %d\n", packet != NULL); + + DPRINTFS(MinorMem, (&port), "Retired packet isRead: %d isWrite: %d" + " needsResponse: %d packetSize: %s requestSize: %s responseSize:" + " %s\n", packet->isRead(), packet->isWrite(), + packet->needsResponse(), packet->getSize(), request.getSize(), + response->getSize()); + + /* A request can become complete by several paths, this is a sanity + * check to make sure the packet's data is created */ + if (!data) { + data = new uint8_t[request.getSize()]; + } + + if (isLoad) { + DPRINTFS(MinorMem, (&port), "Copying read data\n"); + std::memcpy(packet->getPtr<uint8_t>(), data, request.getSize()); + } + packet->makeResponse(); + } + + /* Packets are all deallocated together in ~SplitLSQRequest */ +} + +void +LSQ::SplitDataRequest::sendNextFragmentToTranslation() +{ + unsigned int fragment_index = numTranslatedFragments; + + ThreadContext *thread = port.cpu.getContext( + inst->id.threadId); + + DPRINTFS(MinorMem, (&port), "Submitting DTLB request for fragment: %d\n", + fragment_index); + + port.numAccessesInDTLB++; + numInTranslationFragments++; + + thread->getDTBPtr()->translateTiming( + fragmentRequests[fragment_index], thread, this, (isLoad ? + BaseTLB::Read : BaseTLB::Write)); +} + +bool +LSQ::StoreBuffer::canInsert() const +{ + /* @todo, support store amalgamation */ + return slots.size() < numSlots; +} + +void +LSQ::StoreBuffer::deleteRequest(LSQRequestPtr request) +{ + auto found = std::find(slots.begin(), slots.end(), request); + + if (found != slots.end()) { + DPRINTF(MinorMem, "Deleting request: %s %s %s from StoreBuffer\n", + request, *found, *(request->inst)); + slots.erase(found); + + delete request; + } +} + +void +LSQ::StoreBuffer::insert(LSQRequestPtr request) +{ + if (!canInsert()) { + warn("%s: store buffer insertion without space to insert from" + " inst: %s\n", name(), *(request->inst)); + } + + DPRINTF(MinorMem, "Pushing store: %s into store buffer\n", request); + + numUnissuedAccesses++; + + if (request->state != LSQRequest::Complete) + request->setState(LSQRequest::StoreInStoreBuffer); + + slots.push_back(request); + + /* Let's try and wake up the processor for the next cycle to step + * the store buffer */ + lsq.cpu.wakeupOnEvent(Pipeline::ExecuteStageId); +} + +LSQ::AddrRangeCoverage +LSQ::StoreBuffer::canForwardDataToLoad(LSQRequestPtr request, + unsigned int &found_slot) +{ + unsigned int slot_index = slots.size() - 1; + auto i = slots.rbegin(); + AddrRangeCoverage ret = NoAddrRangeCoverage; + + /* Traverse the store buffer in reverse order (most to least recent) + * and try to find a slot whose address range overlaps this request */ + while (ret == NoAddrRangeCoverage && i != slots.rend()) { + LSQRequestPtr slot = *i; + + if (slot->packet) { + AddrRangeCoverage coverage = slot->containsAddrRangeOf(request); + + if (coverage != NoAddrRangeCoverage) { + DPRINTF(MinorMem, "Forwarding: slot: %d result: %s thisAddr:" + " 0x%x thisSize: %d slotAddr: 0x%x slotSize: %d\n", + slot_index, coverage, + request->request.getPaddr(), request->request.getSize(), + slot->request.getPaddr(), slot->request.getSize()); + + found_slot = slot_index; + ret = coverage; + } + } + + i++; + slot_index--; + } + + return ret; +} + +/** Fill the given packet with appropriate date from slot slot_number */ +void +LSQ::StoreBuffer::forwardStoreData(LSQRequestPtr load, + unsigned int slot_number) +{ + assert(slot_number < slots.size()); + assert(load->packet); + assert(load->isLoad); + + LSQRequestPtr store = slots[slot_number]; + + assert(store->packet); + assert(store->containsAddrRangeOf(load) == FullAddrRangeCoverage); + + Addr load_addr = load->request.getPaddr(); + Addr store_addr = store->request.getPaddr(); + Addr addr_offset = load_addr - store_addr; + + unsigned int load_size = load->request.getSize(); + + DPRINTF(MinorMem, "Forwarding %d bytes for addr: 0x%x from store buffer" + " slot: %d addr: 0x%x addressOffset: 0x%x\n", + load_size, load_addr, slot_number, + store_addr, addr_offset); + + void *load_packet_data = load->packet->getPtr<void>(); + void *store_packet_data = store->packet->getPtr<uint8_t>() + addr_offset; + + std::memcpy(load_packet_data, store_packet_data, load_size); +} + +void +LSQ::StoreBuffer::step() +{ + DPRINTF(MinorMem, "StoreBuffer step numUnissuedAccesses: %d\n", + numUnissuedAccesses); + + if (numUnissuedAccesses != 0 && lsq.state == LSQ::MemoryRunning) { + /* Clear all the leading barriers */ + while (!slots.empty() && + slots.front()->isComplete() && slots.front()->isBarrier()) + { + LSQRequestPtr barrier = slots.front(); + + DPRINTF(MinorMem, "Clearing barrier for inst: %s\n", + *(barrier->inst)); + + numUnissuedAccesses--; + lsq.clearMemBarrier(barrier->inst); + slots.pop_front(); + + delete barrier; + } + + auto i = slots.begin(); + bool issued = true; + unsigned int issue_count = 0; + + /* Skip trying if the memory system is busy */ + if (lsq.state == LSQ::MemoryNeedsRetry) + issued = false; + + /* Try to issue all stores in order starting from the head + * of the queue. Responses are allowed to be retired + * out of order */ + while (issued && + issue_count < storeLimitPerCycle && + lsq.canSendToMemorySystem() && + i != slots.end()) + { + LSQRequestPtr request = *i; + + DPRINTF(MinorMem, "Considering request: %s, sentAllPackets: %d" + " state: %s\n", + *(request->inst), request->sentAllPackets(), + request->state); + + if (request->isBarrier() && request->isComplete()) { + /* Give up at barriers */ + issued = false; + } else if (!(request->state == LSQRequest::StoreBufferIssuing && + request->sentAllPackets())) + { + DPRINTF(MinorMem, "Trying to send request: %s to memory" + " system\n", *(request->inst)); + + if (lsq.tryToSend(request)) { + /* Barrier are accounted for as they are cleared from + * the queue, not after their transfers are complete */ + if (!request->isBarrier()) + numUnissuedAccesses--; + issue_count++; + } else { + /* Don't step on to the next store buffer entry if this + * one hasn't issued all its packets as the store + * buffer must still enforce ordering */ + issued = false; + } + } + i++; + } + } +} + +void +LSQ::completeMemBarrierInst(MinorDynInstPtr inst, + bool committed) +{ + if (committed) { + /* Not already sent to the store buffer as a store request? */ + if (!inst->inStoreBuffer) { + /* Insert an entry into the store buffer to tick off barriers + * until there are none in flight */ + storeBuffer.insert(new BarrierDataRequest(*this, inst)); + } + } else { + /* Clear the barrier anyway if it wasn't actually committed */ + clearMemBarrier(inst); + } +} + +void +LSQ::StoreBuffer::minorTrace() const +{ + unsigned int size = slots.size(); + unsigned int i = 0; + std::ostringstream os; + + while (i < size) { + LSQRequestPtr request = slots[i]; + + request->reportData(os); + + i++; + if (i < numSlots) + os << ','; + } + + while (i < numSlots) { + os << '-'; + + i++; + if (i < numSlots) + os << ','; + } + + MINORTRACE("addr=%s num_unissued_stores=%d\n", os.str(), + numUnissuedAccesses); +} + +void +LSQ::tryToSendToTransfers(LSQRequestPtr request) +{ + if (state == MemoryNeedsRetry) { + DPRINTF(MinorMem, "Request needs retry, not issuing to" + " memory until retry arrives\n"); + return; + } + + if (request->state == LSQRequest::InTranslation) { + DPRINTF(MinorMem, "Request still in translation, not issuing to" + " memory\n"); + return; + } + + assert(request->state == LSQRequest::Translated || + request->state == LSQRequest::RequestIssuing || + request->state == LSQRequest::Failed || + request->state == LSQRequest::Complete); + + if (requests.empty() || requests.front() != request) { + DPRINTF(MinorMem, "Request not at front of requests queue, can't" + " issue to memory\n"); + return; + } + + if (transfers.unreservedRemainingSpace() == 0) { + DPRINTF(MinorMem, "No space to insert request into transfers" + " queue\n"); + return; + } + + if (request->isComplete() || request->state == LSQRequest::Failed) { + DPRINTF(MinorMem, "Passing a %s transfer on to transfers" + " queue\n", (request->isComplete() ? "completed" : "failed")); + request->setState(LSQRequest::Complete); + request->setSkipped(); + moveFromRequestsToTransfers(request); + return; + } + + if (!execute.instIsRightStream(request->inst)) { + /* Wrong stream, try to abort the transfer but only do so if + * there are no packets in flight */ + if (request->hasPacketsInMemSystem()) { + DPRINTF(MinorMem, "Request's inst. is from the wrong stream," + " waiting for responses before aborting request\n"); + } else { + DPRINTF(MinorMem, "Request's inst. is from the wrong stream," + " aborting request\n"); + request->setState(LSQRequest::Complete); + request->setSkipped(); + moveFromRequestsToTransfers(request); + } + return; + } + + if (request->fault != NoFault) { + if (request->inst->staticInst->isPrefetch()) { + DPRINTF(MinorMem, "Not signalling fault for faulting prefetch\n"); + } + DPRINTF(MinorMem, "Moving faulting request into the transfers" + " queue\n"); + request->setState(LSQRequest::Complete); + request->setSkipped(); + moveFromRequestsToTransfers(request); + return; + } + + bool is_load = request->isLoad; + bool is_llsc = request->request.isLLSC(); + bool is_swap = request->request.isSwap(); + bool bufferable = !(request->request.isUncacheable() || + is_llsc || is_swap); + + if (is_load) { + if (numStoresInTransfers != 0) { + DPRINTF(MinorMem, "Load request with stores still in transfers" + " queue, stalling\n"); + return; + } + } else { + /* Store. Can it be sent to the store buffer? */ + if (bufferable && !request->request.isMmappedIpr()) { + request->setState(LSQRequest::StoreToStoreBuffer); + moveFromRequestsToTransfers(request); + DPRINTF(MinorMem, "Moving store into transfers queue\n"); + return; + } + } + + /* Check if this is the head instruction (and so must be executable as + * its stream sequence number was checked above) for loads which must + * not be speculatively issued and stores which must be issued here */ + if (!bufferable) { + if (!execute.instIsHeadInst(request->inst)) { + DPRINTF(MinorMem, "Memory access not the head inst., can't be" + " sure it can be performed, not issuing\n"); + return; + } + + unsigned int forwarding_slot = 0; + + if (storeBuffer.canForwardDataToLoad(request, forwarding_slot) != + NoAddrRangeCoverage) + { + DPRINTF(MinorMem, "Memory access can receive forwarded data" + " from the store buffer, need to wait for store buffer to" + " drain\n"); + return; + } + } + + /* True: submit this packet to the transfers queue to be sent to the + * memory system. + * False: skip the memory and push a packet for this request onto + * requests */ + bool do_access = true; + + if (!is_llsc) { + /* Check for match in the store buffer */ + if (is_load) { + unsigned int forwarding_slot = 0; + AddrRangeCoverage forwarding_result = + storeBuffer.canForwardDataToLoad(request, + forwarding_slot); + + switch (forwarding_result) { + case FullAddrRangeCoverage: + /* Forward data from the store buffer into this request and + * repurpose this request's packet into a response packet */ + storeBuffer.forwardStoreData(request, forwarding_slot); + request->packet->makeResponse(); + + /* Just move between queues, no access */ + do_access = false; + break; + case PartialAddrRangeCoverage: + DPRINTF(MinorMem, "Load partly satisfied by store buffer" + " data. Must wait for the store to complete\n"); + return; + break; + case NoAddrRangeCoverage: + DPRINTF(MinorMem, "No forwardable data from store buffer\n"); + /* Fall through to try access */ + break; + } + } + } else { + if (!canSendToMemorySystem()) { + DPRINTF(MinorMem, "Can't send request to memory system yet\n"); + return; + } + + SimpleThread &thread = *cpu.threads[request->inst->id.threadId]; + + TheISA::PCState old_pc = thread.pcState(); + ExecContext context(cpu, thread, execute, request->inst); + + /* Handle LLSC requests and tests */ + if (is_load) { + TheISA::handleLockedRead(&context, &request->request); + } else { + do_access = TheISA::handleLockedWrite(&context, + &request->request, cacheBlockMask); + + if (!do_access) { + DPRINTF(MinorMem, "Not perfoming a memory " + "access for store conditional\n"); + } + } + thread.pcState(old_pc); + } + + /* See the do_access comment above */ + if (do_access) { + if (!canSendToMemorySystem()) { + DPRINTF(MinorMem, "Can't send request to memory system yet\n"); + return; + } + + /* Remember if this is an access which can't be idly + * discarded by an interrupt */ + if (!bufferable) { + numAccessesIssuedToMemory++; + request->issuedToMemory = true; + } + + if (tryToSend(request)) + moveFromRequestsToTransfers(request); + } else { + request->setState(LSQRequest::Complete); + moveFromRequestsToTransfers(request); + } +} + +bool +LSQ::tryToSend(LSQRequestPtr request) +{ + bool ret = false; + + if (!canSendToMemorySystem()) { + DPRINTF(MinorMem, "Can't send request: %s yet, no space in memory\n", + *(request->inst)); + } else { + PacketPtr packet = request->getHeadPacket(); + + DPRINTF(MinorMem, "Trying to send request: %s addr: 0x%x\n", + *(request->inst), packet->req->getVaddr()); + + /* The sender state of the packet *must* be an LSQRequest + * so the response can be correctly handled */ + assert(packet->findNextSenderState<LSQRequest>()); + + if (request->request.isMmappedIpr()) { + ThreadContext *thread = + cpu.getContext(request->request.threadId()); + + if (request->isLoad) { + DPRINTF(MinorMem, "IPR read inst: %s\n", *(request->inst)); + TheISA::handleIprRead(thread, packet); + } else { + DPRINTF(MinorMem, "IPR write inst: %s\n", *(request->inst)); + TheISA::handleIprWrite(thread, packet); + } + + request->stepToNextPacket(); + ret = request->sentAllPackets(); + + if (!ret) { + DPRINTF(MinorMem, "IPR access has another packet: %s\n", + *(request->inst)); + } + + if (ret) + request->setState(LSQRequest::Complete); + else + request->setState(LSQRequest::RequestIssuing); + } else if (dcachePort.sendTimingReq(packet)) { + DPRINTF(MinorMem, "Sent data memory request\n"); + + numAccessesInMemorySystem++; + + request->stepToNextPacket(); + + ret = request->sentAllPackets(); + + switch (request->state) { + case LSQRequest::Translated: + case LSQRequest::RequestIssuing: + /* Fully or partially issued a request in the transfers + * queue */ + request->setState(LSQRequest::RequestIssuing); + break; + case LSQRequest::StoreInStoreBuffer: + case LSQRequest::StoreBufferIssuing: + /* Fully or partially issued a request in the store + * buffer */ + request->setState(LSQRequest::StoreBufferIssuing); + break; + default: + assert(false); + break; + } + + state = MemoryRunning; + } else { + DPRINTF(MinorMem, + "Sending data memory request - needs retry\n"); + + /* Needs to be resent, wait for that */ + state = MemoryNeedsRetry; + retryRequest = request; + + switch (request->state) { + case LSQRequest::Translated: + case LSQRequest::RequestIssuing: + request->setState(LSQRequest::RequestNeedsRetry); + break; + case LSQRequest::StoreInStoreBuffer: + case LSQRequest::StoreBufferIssuing: + request->setState(LSQRequest::StoreBufferNeedsRetry); + break; + default: + assert(false); + break; + } + } + } + + return ret; +} + +void +LSQ::moveFromRequestsToTransfers(LSQRequestPtr request) +{ + assert(!requests.empty() && requests.front() == request); + assert(transfers.unreservedRemainingSpace() != 0); + + /* Need to count the number of stores in the transfers + * queue so that loads know when their store buffer forwarding + * results will be correct (only when all those stores + * have reached the store buffer) */ + if (!request->isLoad) + numStoresInTransfers++; + + requests.pop(); + transfers.push(request); +} + +bool +LSQ::canSendToMemorySystem() +{ + return state == MemoryRunning && + numAccessesInMemorySystem < inMemorySystemLimit; +} + +bool +LSQ::recvTimingResp(PacketPtr response) +{ + LSQRequestPtr request = + safe_cast<LSQRequestPtr>(response->popSenderState()); + + DPRINTF(MinorMem, "Received response packet inst: %s" + " addr: 0x%x cmd: %s\n", + *(request->inst), response->getAddr(), + response->cmd.toString()); + + numAccessesInMemorySystem--; + + if (response->isError()) { + DPRINTF(MinorMem, "Received error response packet: %s\n", + *request->inst); + } + + switch (request->state) { + case LSQRequest::RequestIssuing: + case LSQRequest::RequestNeedsRetry: + /* Response to a request from the transfers queue */ + request->retireResponse(response); + + DPRINTF(MinorMem, "Has outstanding packets?: %d %d\n", + request->hasPacketsInMemSystem(), request->isComplete()); + + break; + case LSQRequest::StoreBufferIssuing: + case LSQRequest::StoreBufferNeedsRetry: + /* Response to a request from the store buffer */ + request->retireResponse(response); + + /* Remove completed requests unless they are barrier (which will + * need to be removed in order */ + if (request->isComplete()) { + if (!request->isBarrier()) { + storeBuffer.deleteRequest(request); + } else { + DPRINTF(MinorMem, "Completed transfer for barrier: %s" + " leaving the request as it is also a barrier\n", + *(request->inst)); + } + } + break; + default: + /* Shouldn't be allowed to receive a response from another + * state */ + assert(false); + break; + } + + /* We go to idle even if there are more things in the requests queue + * as it's the job of step to actually step us on to the next + * transaction */ + + /* Let's try and wake up the processor for the next cycle */ + cpu.wakeupOnEvent(Pipeline::ExecuteStageId); + + /* Never busy */ + return true; +} + +void +LSQ::recvRetry() +{ + DPRINTF(MinorMem, "Received retry request\n"); + + assert(state == MemoryNeedsRetry); + + switch (retryRequest->state) { + case LSQRequest::RequestNeedsRetry: + /* Retry in the requests queue */ + retryRequest->setState(LSQRequest::Translated); + break; + case LSQRequest::StoreBufferNeedsRetry: + /* Retry in the store buffer */ + retryRequest->setState(LSQRequest::StoreInStoreBuffer); + break; + default: + assert(false); + } + + /* Set state back to MemoryRunning so that the following + * tryToSend can actually send. Note that this won't + * allow another transfer in as tryToSend should + * issue a memory request and either succeed for this + * request or return the LSQ back to MemoryNeedsRetry */ + state = MemoryRunning; + + /* Try to resend the request */ + if (tryToSend(retryRequest)) { + /* Successfully sent, need to move the request */ + switch (retryRequest->state) { + case LSQRequest::RequestIssuing: + /* In the requests queue */ + moveFromRequestsToTransfers(retryRequest); + break; + case LSQRequest::StoreBufferIssuing: + /* In the store buffer */ + storeBuffer.numUnissuedAccesses--; + break; + default: + assert(false); + break; + } + } + + retryRequest = NULL; +} + +LSQ::LSQ(std::string name_, std::string dcache_port_name_, + MinorCPU &cpu_, Execute &execute_, + unsigned int in_memory_system_limit, unsigned int line_width, + unsigned int requests_queue_size, unsigned int transfers_queue_size, + unsigned int store_buffer_size, + unsigned int store_buffer_cycle_store_limit) : + Named(name_), + cpu(cpu_), + execute(execute_), + dcachePort(dcache_port_name_, *this, cpu_), + lastMemBarrier(0), + state(MemoryRunning), + inMemorySystemLimit(in_memory_system_limit), + lineWidth((line_width == 0 ? cpu.cacheLineSize() : line_width)), + requests(name_ + ".requests", "addr", requests_queue_size), + transfers(name_ + ".transfers", "addr", transfers_queue_size), + storeBuffer(name_ + ".storeBuffer", + *this, store_buffer_size, store_buffer_cycle_store_limit), + numAccessesInMemorySystem(0), + numAccessesInDTLB(0), + numStoresInTransfers(0), + numAccessesIssuedToMemory(0), + retryRequest(NULL), + cacheBlockMask(~(cpu_.cacheLineSize() - 1)) +{ + if (in_memory_system_limit < 1) { + fatal("%s: executeMaxAccessesInMemory must be >= 1 (%d)\n", name_, + in_memory_system_limit); + } + + if (store_buffer_cycle_store_limit < 1) { + fatal("%s: executeLSQMaxStoreBufferStoresPerCycle must be" + " >= 1 (%d)\n", name_, store_buffer_cycle_store_limit); + } + + if (requests_queue_size < 1) { + fatal("%s: executeLSQRequestsQueueSize must be" + " >= 1 (%d)\n", name_, requests_queue_size); + } + + if (transfers_queue_size < 1) { + fatal("%s: executeLSQTransfersQueueSize must be" + " >= 1 (%d)\n", name_, transfers_queue_size); + } + + if (store_buffer_size < 1) { + fatal("%s: executeLSQStoreBufferSize must be" + " >= 1 (%d)\n", name_, store_buffer_size); + } + + if ((lineWidth & (lineWidth - 1)) != 0) { + fatal("%s: lineWidth: %d must be a power of 2\n", name(), lineWidth); + } +} + +LSQ::~LSQ() +{ } + +LSQ::LSQRequest::~LSQRequest() +{ + if (packet) + delete packet; + if (data) + delete [] data; +} + +/** + * Step the memory access mechanism on to its next state. In reality, most + * of the stepping is done by the callbacks on the LSQ but this + * function is responsible for issuing memory requests lodged in the + * requests queue. + */ +void +LSQ::step() +{ + /* Try to move address-translated requests between queues and issue + * them */ + if (!requests.empty()) + tryToSendToTransfers(requests.front()); + + storeBuffer.step(); +} + +LSQ::LSQRequestPtr +LSQ::findResponse(MinorDynInstPtr inst) +{ + LSQ::LSQRequestPtr ret = NULL; + + if (!transfers.empty()) { + LSQRequestPtr request = transfers.front(); + + /* Same instruction and complete access or a store that's + * capable of being moved to the store buffer */ + if (request->inst->id == inst->id) { + if (request->isComplete() || + (request->state == LSQRequest::StoreToStoreBuffer && + storeBuffer.canInsert())) + { + ret = request; + } + } + } + + if (ret) { + DPRINTF(MinorMem, "Found matching memory response for inst: %s\n", + *inst); + } else { + DPRINTF(MinorMem, "No matching memory response for inst: %s\n", + *inst); + } + + return ret; +} + +void +LSQ::popResponse(LSQ::LSQRequestPtr response) +{ + assert(!transfers.empty() && transfers.front() == response); + + transfers.pop(); + + if (!response->isLoad) + numStoresInTransfers--; + + if (response->issuedToMemory) + numAccessesIssuedToMemory--; + + if (response->state != LSQRequest::StoreInStoreBuffer) { + DPRINTF(MinorMem, "Deleting %s request: %s\n", + (response->isLoad ? "load" : "store"), + *(response->inst)); + + delete response; + } +} + +void +LSQ::sendStoreToStoreBuffer(LSQRequestPtr request) +{ + assert(request->state == LSQRequest::StoreToStoreBuffer); + + DPRINTF(MinorMem, "Sending store: %s to store buffer\n", + *(request->inst)); + + request->inst->inStoreBuffer = true; + + storeBuffer.insert(request); +} + +bool +LSQ::isDrained() +{ + return requests.empty() && transfers.empty() && + storeBuffer.isDrained(); +} + +bool +LSQ::needsToTick() +{ + bool ret = false; + + if (canSendToMemorySystem()) { + bool have_translated_requests = !requests.empty() && + requests.front()->state != LSQRequest::InTranslation && + transfers.unreservedRemainingSpace() != 0; + + ret = have_translated_requests || + storeBuffer.numUnissuedStores() != 0; + } + + if (ret) + DPRINTF(Activity, "Need to tick\n"); + + return ret; +} + +void +LSQ::pushRequest(MinorDynInstPtr inst, bool isLoad, uint8_t *data, + unsigned int size, Addr addr, unsigned int flags, uint64_t *res) +{ + bool needs_burst = transferNeedsBurst(addr, size, lineWidth); + LSQRequestPtr request; + + /* Copy given data into the request. The request will pass this to the + * packet and then it will own the data */ + uint8_t *request_data = NULL; + + DPRINTF(MinorMem, "Pushing request (%s) addr: 0x%x size: %d flags:" + " 0x%x%s lineWidth : 0x%x\n", + (isLoad ? "load" : "store"), addr, size, flags, + (needs_burst ? " (needs burst)" : ""), lineWidth); + + if (!isLoad) { + /* request_data becomes the property of a ...DataRequest (see below) + * and destroyed by its destructor */ + request_data = new uint8_t[size]; + if (flags & Request::CACHE_BLOCK_ZERO) { + /* For cache zeroing, just use zeroed data */ + std::memset(request_data, 0, size); + } else { + std::memcpy(request_data, data, size); + } + } + + if (needs_burst) { + request = new SplitDataRequest( + *this, inst, isLoad, request_data, res); + } else { + request = new SingleDataRequest( + *this, inst, isLoad, request_data, res); + } + + if (inst->traceData) + inst->traceData->setAddr(addr); + + request->request.setThreadContext(cpu.cpuId(), /* thread id */ 0); + request->request.setVirt(0 /* asid */, + addr, size, flags, cpu.instMasterId(), + /* I've no idea why we need the PC, but give it */ + inst->pc.instAddr()); + + requests.push(request); + request->startAddrTranslation(); +} + +void +LSQ::pushFailedRequest(MinorDynInstPtr inst) +{ + LSQRequestPtr request = new FailedDataRequest(*this, inst); + requests.push(request); +} + +void +LSQ::minorTrace() const +{ + MINORTRACE("state=%s in_tlb_mem=%d/%d stores_in_transfers=%d" + " lastMemBarrier=%d\n", + state, numAccessesInDTLB, numAccessesInMemorySystem, + numStoresInTransfers, lastMemBarrier); + requests.minorTrace(); + transfers.minorTrace(); + storeBuffer.minorTrace(); +} + +LSQ::StoreBuffer::StoreBuffer(std::string name_, LSQ &lsq_, + unsigned int store_buffer_size, + unsigned int store_limit_per_cycle) : + Named(name_), lsq(lsq_), + numSlots(store_buffer_size), + storeLimitPerCycle(store_limit_per_cycle), + slots(), + numUnissuedAccesses(0) +{ +} + +PacketPtr +makePacketForRequest(Request &request, bool isLoad, + Packet::SenderState *sender_state, PacketDataPtr data) +{ + MemCmd command; + + /* Make a ret with the right command type to match the request */ + if (request.isLLSC()) { + command = (isLoad ? MemCmd::LoadLockedReq : MemCmd::StoreCondReq); + } else if (request.isSwap()) { + command = MemCmd::SwapReq; + } else { + command = (isLoad ? MemCmd::ReadReq : MemCmd::WriteReq); + } + + PacketPtr ret = new Packet(&request, command); + + if (sender_state) + ret->pushSenderState(sender_state); + + if (isLoad) + ret->allocate(); + else + ret->dataDynamicArray(data); + + return ret; +} + +void +LSQ::issuedMemBarrierInst(MinorDynInstPtr inst) +{ + assert(inst->isInst() && inst->staticInst->isMemBarrier()); + assert(inst->id.execSeqNum > lastMemBarrier); + + /* Remember the barrier. We only have a notion of one + * barrier so this may result in some mem refs being + * delayed if they are between barriers */ + lastMemBarrier = inst->id.execSeqNum; +} + +void +LSQ::LSQRequest::makePacket() +{ + /* Make the function idempotent */ + if (packet) + return; + + packet = makePacketForRequest(request, isLoad, this, data); + /* Null the ret data so we know not to deallocate it when the + * ret is destroyed. The data now belongs to the ret and + * the ret is responsible for its destruction */ + data = NULL; +} + +std::ostream & +operator <<(std::ostream &os, LSQ::MemoryState state) +{ + switch (state) { + case LSQ::MemoryRunning: + os << "MemoryRunning"; + break; + case LSQ::MemoryNeedsRetry: + os << "MemoryNeedsRetry"; + break; + default: + os << "MemoryState-" << static_cast<int>(state); + break; + } + return os; +} + +void +LSQ::recvTimingSnoopReq(PacketPtr pkt) +{ + /* LLSC operations in Minor can't be speculative and are executed from + * the head of the requests queue. We shouldn't need to do more than + * this action on snoops. */ + + /* THREAD */ + TheISA::handleLockedSnoop(cpu.getContext(0), pkt, cacheBlockMask); +} + +} diff --git a/src/cpu/minor/lsq.hh b/src/cpu/minor/lsq.hh new file mode 100644 index 000000000..0998395e0 --- /dev/null +++ b/src/cpu/minor/lsq.hh @@ -0,0 +1,722 @@ +/* + * Copyright (c) 2013-2014 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: Andrew Bardsley + */ + +/** + * @file + * + * A load/store queue that allows outstanding reads and writes. + * + */ + +#ifndef __CPU_MINOR_NEW_LSQ_HH__ +#define __CPU_MINOR_NEW_LSQ_HH__ + +#include "cpu/minor/buffers.hh" +#include "cpu/minor/cpu.hh" +#include "cpu/minor/pipe_data.hh" +#include "cpu/minor/trace.hh" + +namespace Minor +{ + +/* Forward declaration */ +class Execute; + +class LSQ : public Named +{ + protected: + /** My owner(s) */ + MinorCPU &cpu; + Execute &execute; + + protected: + /** State of memory access for head access. */ + enum MemoryState + { + MemoryRunning, /* Default. Step dcache queues when possible. */ + MemoryNeedsRetry /* Request rejected, will be asked to retry */ + }; + + /** Print MemoryState values as shown in the enum definition */ + friend std::ostream &operator <<(std::ostream &os, + MemoryState state); + + /** Coverage of one address range with another */ + enum AddrRangeCoverage + { + PartialAddrRangeCoverage, /* Two ranges partly overlap */ + FullAddrRangeCoverage, /* One range fully covers another */ + NoAddrRangeCoverage /* Two ranges are disjoint */ + }; + + /** Exposable data port */ + class DcachePort : public MinorCPU::MinorCPUPort + { + protected: + /** My owner */ + LSQ &lsq; + + public: + DcachePort(std::string name, LSQ &lsq_, MinorCPU &cpu) : + MinorCPU::MinorCPUPort(name, cpu), lsq(lsq_) + { } + + protected: + bool recvTimingResp(PacketPtr pkt) + { return lsq.recvTimingResp(pkt); } + + void recvRetry() { lsq.recvRetry(); } + + void recvTimingSnoopReq(PacketPtr pkt) + { return lsq.recvTimingSnoopReq(pkt); } + }; + + DcachePort dcachePort; + + public: + /** Derived SenderState to carry data access info. through address + * translation, the queues in this port and back from the memory + * system. */ + class LSQRequest : + public BaseTLB::Translation, /* For TLB lookups */ + public Packet::SenderState /* For packing into a Packet */ + { + public: + /** Owning port */ + LSQ &port; + + /** Instruction which made this request */ + MinorDynInstPtr inst; + + /** Load/store indication used for building packet. This isn't + * carried by Request so we need to keep it here */ + bool isLoad; + + /** Dynamically allocated and populated data carried for + * building write packets */ + PacketDataPtr data; + + /* Requests carry packets on their way to the memory system. + * When a Packet returns from the memory system, its + * request needs to have its packet updated as this + * may have changed in flight */ + PacketPtr packet; + + /** The underlying request of this LSQRequest */ + Request request; + + /** Fault generated performing this request */ + Fault fault; + + /** Res from pushRequest */ + uint64_t *res; + + /** Was skipped. Set to indicate any reason (faulted, bad + * stream sequence number, in a fault shadow) that this + * request did not perform a memory transfer */ + bool skipped; + + /** This in an access other than a normal cacheable load + * that's visited the memory system */ + bool issuedToMemory; + + enum LSQRequestState + { + NotIssued, /* Newly created */ + InTranslation, /* TLB accessed, no reply yet */ + Translated, /* Finished address translation */ + Failed, /* The starting start of FailedDataRequests */ + RequestIssuing, /* Load/store issued to memory in the requests + queue */ + StoreToStoreBuffer, /* Store in transfers on its way to the + store buffer */ + RequestNeedsRetry, /* Retry needed for load */ + StoreInStoreBuffer, /* Store in the store buffer, before issuing + a memory transfer */ + StoreBufferIssuing, /* Store in store buffer and has been + issued */ + StoreBufferNeedsRetry, /* Retry needed for store */ + /* All completed states. Includes + completed loads, TLB faults and skipped requests whose + seqNum's no longer match */ + Complete + }; + + LSQRequestState state; + + protected: + /** BaseTLB::Translation interface */ + void markDelayed() { } + + public: + LSQRequest(LSQ &port_, MinorDynInstPtr inst_, bool isLoad_, + PacketDataPtr data_ = NULL, uint64_t *res_ = NULL); + + virtual ~LSQRequest(); + + public: + /** Make a packet to use with the memory transaction */ + void makePacket(); + + /** Was no memory access attempted for this request? */ + bool skippedMemAccess() { return skipped; } + + /** Set this request as having been skipped before a memory + * transfer was attempt */ + void setSkipped() { skipped = true; } + + /** Does address range req1 (req1_addr to req1_addr + req1_size - 1) + * fully cover, partially cover or not cover at all the range req2 */ + static AddrRangeCoverage containsAddrRangeOf( + Addr req1_addr, unsigned int req1_size, + Addr req2_addr, unsigned int req2_size); + + /** Does this request's address range fully cover the range + * of other_request? */ + AddrRangeCoverage containsAddrRangeOf(LSQRequest *other_request); + + /** Start the address translation process for this request. This + * will issue a translation request to the TLB. */ + virtual void startAddrTranslation() = 0; + + /** Get the next packet to issue for this request. For split + * transfers, it will be necessary to step through the available + * packets by calling do { getHeadPacket ; stepToNextPacket } while + * (!sentAllPackets) and by retiring response using retireResponse */ + virtual PacketPtr getHeadPacket() = 0; + + /** Step to the next packet for the next call to getHeadPacket */ + virtual void stepToNextPacket() = 0; + + /** Have all packets been sent? */ + virtual bool sentAllPackets() = 0; + + /** True if this request has any issued packets in the memory + * system and so can't be interrupted until it gets responses */ + virtual bool hasPacketsInMemSystem() = 0; + + /** Retire a response packet into the LSQRequest packet possibly + * completing this transfer */ + virtual void retireResponse(PacketPtr packet_) = 0; + + /** Is this a request a barrier? */ + virtual bool isBarrier(); + + /** This request, once processed by the requests/transfers + * queues, will need to go to the store buffer */ + bool needsToBeSentToStoreBuffer(); + + /** Set state and output trace output */ + void setState(LSQRequestState new_state); + + /** Has this request been completed. This includes *all* reasons + * for completion: successful transfers, faults, skipped because + * of preceding faults */ + bool isComplete() const; + + /** MinorTrace report interface */ + void reportData(std::ostream &os) const; + }; + + typedef LSQRequest *LSQRequestPtr; + + friend std::ostream & operator <<(std::ostream &os, + AddrRangeCoverage state); + + friend std::ostream & operator <<(std::ostream &os, + LSQRequest::LSQRequestState state); + + protected: + /** Special request types that don't actually issue memory requests */ + class SpecialDataRequest : public LSQRequest + { + protected: + /** TLB interace */ + void finish(Fault fault_, RequestPtr request_, ThreadContext *tc, + BaseTLB::Mode mode) + { } + + public: + /** Send single translation request */ + void startAddrTranslation() { } + + /** Get the head packet as counted by numIssuedFragments */ + PacketPtr getHeadPacket() + { fatal("No packets in a SpecialDataRequest"); } + + /** Step on numIssuedFragments */ + void stepToNextPacket() { } + + /** Has no packets to send */ + bool sentAllPackets() { return true; } + + /** Never sends any requests */ + bool hasPacketsInMemSystem() { return false; } + + /** Keep the given packet as the response packet + * LSQRequest::packet */ + void retireResponse(PacketPtr packet_) { } + + public: + SpecialDataRequest(LSQ &port_, MinorDynInstPtr inst_) : + /* Say this is a load, not actually relevant */ + LSQRequest(port_, inst_, true, NULL, 0) + { } + }; + + /** FailedDataRequest represents requests from instructions that + * failed their predicates but need to ride the requests/transfers + * queues to maintain trace ordering */ + class FailedDataRequest : public SpecialDataRequest + { + public: + FailedDataRequest(LSQ &port_, MinorDynInstPtr inst_) : + SpecialDataRequest(port_, inst_) + { state = Failed; } + }; + + /** Request for doing barrier accounting in the store buffer. Not + * for use outside that unit */ + class BarrierDataRequest : public SpecialDataRequest + { + public: + bool isBarrier() { return true; } + + public: + BarrierDataRequest(LSQ &port_, MinorDynInstPtr inst_) : + SpecialDataRequest(port_, inst_) + { state = Complete; } + }; + + /** SingleDataRequest is used for requests that don't fragment */ + class SingleDataRequest : public LSQRequest + { + protected: + /** TLB interace */ + void finish(Fault fault_, RequestPtr request_, ThreadContext *tc, + BaseTLB::Mode mode); + + /** Has my only packet been sent to the memory system but has not + * yet been responded to */ + bool packetInFlight; + + /** Has the packet been at least sent to the memory system? */ + bool packetSent; + + public: + /** Send single translation request */ + void startAddrTranslation(); + + /** Get the head packet as counted by numIssuedFragments */ + PacketPtr getHeadPacket() { return packet; } + + /** Remember that the packet has been sent */ + void stepToNextPacket() { packetInFlight = true; packetSent = true; } + + /** Has packet been sent */ + bool hasPacketsInMemSystem() { return packetInFlight; } + + /** packetInFlight can become false again, so need to check + * packetSent */ + bool sentAllPackets() { return packetSent; } + + /** Keep the given packet as the response packet + * LSQRequest::packet */ + void retireResponse(PacketPtr packet_); + + public: + SingleDataRequest(LSQ &port_, MinorDynInstPtr inst_, + bool isLoad_, PacketDataPtr data_ = NULL, uint64_t *res_ = NULL) : + LSQRequest(port_, inst_, isLoad_, data_, res_), + packetInFlight(false), + packetSent(false) + { } + }; + + class SplitDataRequest : public LSQRequest + { + protected: + /** Event to step between translations */ + class TranslationEvent : public Event + { + protected: + SplitDataRequest &owner; + + public: + TranslationEvent(SplitDataRequest &owner_) + : owner(owner_) { } + + void process() + { owner.sendNextFragmentToTranslation(); } + }; + + TranslationEvent translationEvent; + protected: + /** Number of fragments this request is split into */ + unsigned int numFragments; + + /** Number of fragments in the address translation mechanism */ + unsigned int numInTranslationFragments; + + /** Number of fragments that have completed address translation, + * (numTranslatedFragments + numInTranslationFragments) <= + * numFragments. When numTranslatedFramgents == numFragments, + * translation is complete */ + unsigned int numTranslatedFragments; + + /** Number of fragments already issued (<= numFragments) */ + unsigned int numIssuedFragments; + + /** Number of fragments retired back to this request */ + unsigned int numRetiredFragments; + + /** Fragment Requests corresponding to the address ranges of + * each fragment */ + std::vector<Request *> fragmentRequests; + + /** Packets matching fragmentRequests to issue fragments to memory */ + std::vector<Packet *> fragmentPackets; + + protected: + /** TLB response interface */ + void finish(Fault fault_, RequestPtr request_, ThreadContext *tc, + BaseTLB::Mode mode); + + public: + SplitDataRequest(LSQ &port_, MinorDynInstPtr inst_, + bool isLoad_, PacketDataPtr data_ = NULL, + uint64_t *res_ = NULL); + + ~SplitDataRequest(); + + public: + /** Make all the Requests for this transfer's fragments so that those + * requests can be sent for address translation */ + void makeFragmentRequests(); + + /** Make the packets to go with the requests so they can be sent to + * the memory system */ + void makeFragmentPackets(); + + /** Start a loop of do { sendNextFragmentToTranslation ; + * translateTiming ; finish } while (numTranslatedFragments != + * numFragments) to complete all this requests' fragments' address + * translations */ + void startAddrTranslation(); + + /** Get the head packet as counted by numIssuedFragments */ + PacketPtr getHeadPacket(); + + /** Step on numIssuedFragments */ + void stepToNextPacket(); + + bool hasPacketsInMemSystem() + { return numIssuedFragments != numRetiredFragments; } + + /** Have we stepped past the end of fragmentPackets? */ + bool sentAllPackets() { return numIssuedFragments == numFragments; } + + /** For loads, paste the response data into the main + * response packet */ + void retireResponse(PacketPtr packet_); + + /** Part of the address translation loop, see startAddTranslation */ + void sendNextFragmentToTranslation(); + }; + + /** Store buffer. This contains stores which have been committed + * but whose memory transfers have not yet been issued. Load data + * can be forwarded out of the store buffer */ + class StoreBuffer : public Named + { + public: + /** My owner */ + LSQ &lsq; + + /** Number of slots, this is a bound on the size of slots */ + const unsigned int numSlots; + + /** Maximum number of stores that can be issued per cycle */ + const unsigned int storeLimitPerCycle; + + public: + /** Queue of store requests on their way to memory */ + std::deque<LSQRequestPtr> slots; + + /** Number of occupied slots which have not yet issued a + * memory access */ + unsigned int numUnissuedAccesses; + + public: + StoreBuffer(std::string name_, LSQ &lsq_, + unsigned int store_buffer_size, + unsigned int store_limit_per_cycle); + + public: + /** Can a new request be inserted into the queue? */ + bool canInsert() const; + + /** Delete the given request and free the slot it occupied */ + void deleteRequest(LSQRequestPtr request); + + /** Insert a request at the back of the queue */ + void insert(LSQRequestPtr request); + + /** Look for a store which satisfies the given load. Returns an + * indication whether the forwarding request can be wholly, + * partly or not all all satisfied. If the request can be + * wholly satisfied, the store buffer slot number which can be used + * is returned in found_slot */ + AddrRangeCoverage canForwardDataToLoad(LSQRequestPtr request, + unsigned int &found_slot); + + /** Fill the given packet with appropriate date from slot + * slot_number */ + void forwardStoreData(LSQRequestPtr load, unsigned int slot_number); + + /** Number of stores in the store buffer which have not been + * completely issued to the memory system */ + unsigned int numUnissuedStores() { return numUnissuedAccesses; } + + /** Drained if there is absolutely nothing left in the buffer */ + bool isDrained() const { return slots.empty(); } + + /** Try to issue more stores to memory */ + void step(); + + /** Report queue contents for MinorTrace */ + void minorTrace() const; + }; + + protected: + /** Most recent execSeqNum of a memory barrier instruction or + * 0 if there are no in-flight barriers. Useful as a + * dependency for early-issued memory operations */ + InstSeqNum lastMemBarrier; + + public: + /** Retry state of last issued memory transfer */ + MemoryState state; + + /** Maximum number of in-flight accesses issued to the memory system */ + const unsigned int inMemorySystemLimit; + + /** Memory system access width (and snap) in bytes */ + const unsigned int lineWidth; + + public: + /** The LSQ consists of three queues: requests, transfers and the + * store buffer storeBuffer. */ + + typedef Queue<LSQRequestPtr, + ReportTraitsPtrAdaptor<LSQRequestPtr>, + NoBubbleTraits<LSQRequestPtr> > + LSQQueue; + + /** requests contains LSQRequests which have been issued to the TLB by + * calling ExecContext::readMem/writeMem (which in turn calls + * LSQ::pushRequest and LSQRequest::startAddrTranslation). Once they + * have a physical address, requests at the head of requests can be + * issued to the memory system. At this stage, it cannot be clear that + * memory accesses *must* happen (that there are no preceding faults or + * changes of flow of control) and so only cacheable reads are issued + * to memory. + * Cacheable stores are not issued at all (and just pass through + * 'transfers' in order) and all other transfers are stalled in requests + * until their corresponding instructions are at the head of the + * inMemInsts instruction queue and have the right streamSeqNum. */ + LSQQueue requests; + + /** Once issued to memory (or, for stores, just had their + * state changed to StoreToStoreBuffer) LSQRequests pass through + * transfers waiting for memory responses. At the head of transfers, + * Execute::commitInst can pick up the memory response for a request + * using LSQ::findResponse. Responses to be committed can then + * have ExecContext::completeAcc on them. Stores can then be pushed + * into the store buffer. All other transfers will then be complete. */ + LSQQueue transfers; + + /* The store buffer contains committed cacheable stores on + * their way to memory decoupled from subsequence instruction execution. + * Before trying to issue a cacheable read from 'requests' to memory, + * the store buffer is checked to see if a previous store contains the + * needed data (StoreBuffer::canForwardDataToLoad) which can be + * forwarded in lieu of a memory access. If there are outstanding + * stores in the transfers queue, they must be promoted to the store + * buffer (and so be commited) before they can be correctly checked + * for forwarding. */ + StoreBuffer storeBuffer; + + protected: + /** Count of the number of mem. accesses which have left the + * requests queue and are in the 'wild' in the memory system. */ + unsigned int numAccessesInMemorySystem; + + /** Number of requests in the DTLB in the requests queue */ + unsigned int numAccessesInDTLB; + + /** The number of stores in the transfers queue. Useful when + * testing if the store buffer contains all the forwardable stores */ + unsigned int numStoresInTransfers; + + /** The number of accesses which have been issued to the memory + * system but have not been committed/discarded *excluding* + * cacheable normal loads which don't need to be tracked */ + unsigned int numAccessesIssuedToMemory; + + /** The request (from either requests or the store buffer) which is + * currently waiting have its memory access retried */ + LSQRequestPtr retryRequest; + + /** Address Mask for a cache block (e.g. ~(cache_block_size-1)) */ + Addr cacheBlockMask; + + protected: + /** Try and issue a memory access for a translated request at the + * head of the requests queue. Also tries to move the request + * between queues */ + void tryToSendToTransfers(LSQRequestPtr request); + + /** Try to send (or resend) a memory request's next/only packet to + * the memory system. Returns true if the request was successfully + * sent to memory (and was also the last packet in a transfer) */ + bool tryToSend(LSQRequestPtr request); + + /** Clear a barrier (if it's the last one marked up in lastMemBarrier) */ + void clearMemBarrier(MinorDynInstPtr inst); + + /** Move a request between queues */ + void moveFromRequestsToTransfers(LSQRequestPtr request); + + /** Can a request be sent to the memory system */ + bool canSendToMemorySystem(); + + public: + LSQ(std::string name_, std::string dcache_port_name_, + MinorCPU &cpu_, Execute &execute_, + unsigned int max_accesses_in_memory_system, unsigned int line_width, + unsigned int requests_queue_size, unsigned int transfers_queue_size, + unsigned int store_buffer_size, + unsigned int store_buffer_cycle_store_limit); + + virtual ~LSQ(); + + public: + /** Step checks the queues to see if their are issuable transfers + * which were not otherwise picked up by tests at the end of other + * events. + * + * Steppable actions include deferred actions which couldn't be + * cascaded on the end of a memory response/TLB response event + * because of resource congestion. */ + void step(); + + /** Is their space in the request queue to be able to push a request by + * issuing an isMemRef instruction */ + bool canRequest() { return requests.unreservedRemainingSpace() != 0; } + + /** Returns a response if it's at the head of the transfers queue and + * it's either complete or can be sent on to the store buffer. After + * calling, the request still remains on the transfer queue until + * popResponse is called */ + LSQRequestPtr findResponse(MinorDynInstPtr inst); + + /** Sanity check and pop the head response */ + void popResponse(LSQRequestPtr response); + + /** Must check this before trying to insert into the store buffer */ + bool canPushIntoStoreBuffer() const { return storeBuffer.canInsert(); } + + /** A store has been committed, please move it to the store buffer */ + void sendStoreToStoreBuffer(LSQRequestPtr request); + + /** Are there any accesses other than normal cached loads in the + * memory system or having received responses which need to be + * handled for their instruction's to be completed */ + bool accessesInFlight() const + { return numAccessesIssuedToMemory != 0; } + + /** A memory barrier instruction has been issued, remember its + * execSeqNum that we can avoid issuing memory ops until it is + * committed */ + void issuedMemBarrierInst(MinorDynInstPtr inst); + + /** Get the execSeqNum of the last issued memory barrier */ + InstSeqNum getLastMemBarrier() const { return lastMemBarrier; } + + /** Is there nothing left in the LSQ */ + bool isDrained(); + + /** May need to be ticked next cycle as one of the queues contains + * an actionable transfers or address translation */ + bool needsToTick(); + + /** Complete a barrier instruction. Where committed, makes a + * BarrierDataRequest and pushed it into the store buffer */ + void completeMemBarrierInst(MinorDynInstPtr inst, + bool committed); + + /** Single interface for readMem/writeMem to issue requests into + * the LSQ */ + void pushRequest(MinorDynInstPtr inst, bool isLoad, uint8_t *data, + unsigned int size, Addr addr, unsigned int flags, uint64_t *res); + + /** Push a predicate failed-representing request into the queues just + * to maintain commit order */ + void pushFailedRequest(MinorDynInstPtr inst); + + /** Memory interface */ + bool recvTimingResp(PacketPtr pkt); + void recvRetry(); + void recvTimingSnoopReq(PacketPtr pkt); + + /** Return the raw-bindable port */ + MinorCPU::MinorCPUPort &getDcachePort() { return dcachePort; } + + void minorTrace() const; +}; + +/** Make a suitable packet for the given request. If the request is a store, + * data will be the payload data. If sender_state is NULL, it won't be + * pushed into the packet as senderState */ +PacketPtr makePacketForRequest(Request &request, bool isLoad, + Packet::SenderState *sender_state = NULL, PacketDataPtr data = NULL); +} + +#endif /* __CPU_MINOR_NEW_LSQ_HH__ */ diff --git a/src/cpu/minor/pipe_data.cc b/src/cpu/minor/pipe_data.cc new file mode 100644 index 000000000..447f9c0e7 --- /dev/null +++ b/src/cpu/minor/pipe_data.cc @@ -0,0 +1,294 @@ +/* + * Copyright (c) 2013-2014 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: Andrew Bardsley + */ + +#include "cpu/minor/pipe_data.hh" + +namespace Minor +{ + +std::ostream & +operator <<(std::ostream &os, BranchData::Reason reason) +{ + switch (reason) + { + case BranchData::NoBranch: + os << "NoBranch"; + break; + case BranchData::UnpredictedBranch: + os << "UnpredictedBranch"; + break; + case BranchData::BranchPrediction: + os << "BranchPrediction"; + break; + case BranchData::CorrectlyPredictedBranch: + os << "CorrectlyPredictedBranch"; + break; + case BranchData::BadlyPredictedBranch: + os << "BadlyPredictedBranch"; + break; + case BranchData::BadlyPredictedBranchTarget: + os << "BadlyPredictedBranchTarget"; + break; + case BranchData::Interrupt: + os << "Interrupt"; + break; + case BranchData::SuspendThread: + os << "SuspendThread"; + break; + case BranchData::WakeupFetch: + os << "WakeupFetch"; + break; + case BranchData::HaltFetch: + os << "HaltFetch"; + break; + } + + return os; +} + +bool +BranchData::isStreamChange(const BranchData::Reason reason) +{ + bool ret = false; + + switch (reason) + { + /* No change of stream (see the enum comment in pipe_data.hh) */ + case NoBranch: + case CorrectlyPredictedBranch: + ret = false; + break; + + /* Change of stream (Fetch1 should act on) */ + case UnpredictedBranch: + case BranchPrediction: + case BadlyPredictedBranchTarget: + case BadlyPredictedBranch: + case SuspendThread: + case Interrupt: + case WakeupFetch: + case HaltFetch: + ret = true; + break; + } + + return ret; +} + +bool +BranchData::isBranch(const BranchData::Reason reason) +{ + bool ret = false; + + switch (reason) + { + /* No change of stream (see the enum comment in pipe_data.hh) */ + case NoBranch: + case CorrectlyPredictedBranch: + case SuspendThread: + case Interrupt: + case WakeupFetch: + case HaltFetch: + ret = false; + break; + + /* Change of stream (Fetch1 should act on) */ + case UnpredictedBranch: + case BranchPrediction: + case BadlyPredictedBranchTarget: + case BadlyPredictedBranch: + ret = true; + break; + } + + return ret; +} + +void +BranchData::reportData(std::ostream &os) const +{ + if (isBubble()) { + os << '-'; + } else { + os << reason + << ';' << newStreamSeqNum << '.' << newPredictionSeqNum + << ";0x" << std::hex << target.instAddr() << std::dec + << ';'; + inst->reportData(os); + } +} + +std::ostream & +operator <<(std::ostream &os, const BranchData &branch) +{ + os << branch.reason << " target: 0x" + << std::hex << branch.target.instAddr() << std::dec + << ' ' << *branch.inst + << ' ' << branch.newStreamSeqNum << "(stream)." + << branch.newPredictionSeqNum << "(pred)"; + + return os; +} + +void +ForwardLineData::setFault(Fault fault_) +{ + fault = fault_; + if (isFault()) + bubbleFlag = false; +} + +void +ForwardLineData::allocateLine(unsigned int width_) +{ + lineWidth = width_; + bubbleFlag = false; + + assert(!isFault()); + assert(!line); + + line = new uint8_t[width_]; +} + +void +ForwardLineData::adoptPacketData(Packet *packet) +{ + this->packet = packet; + lineWidth = packet->req->getSize(); + bubbleFlag = false; + + assert(!isFault()); + assert(!line); + + line = packet->getPtr<uint8_t>(); +} + +void +ForwardLineData::freeLine() +{ + /* Only free lines in non-faulting, non-bubble lines */ + if (!isFault() && !isBubble()) { + assert(line); + /* If packet is not NULL then the line must belong to the packet so + * we don't need to separately deallocate the line */ + if (packet) { + delete packet; + } else { + delete [] line; + } + line = NULL; + bubbleFlag = true; + } +} + +void +ForwardLineData::reportData(std::ostream &os) const +{ + if (isBubble()) + os << '-'; + else if (fault != NoFault) + os << "F;" << id; + else + os << id; +} + +ForwardInstData::ForwardInstData(unsigned int width) : + numInsts(width) +{ + bubbleFill(); +} + +ForwardInstData::ForwardInstData(const ForwardInstData &src) +{ + *this = src; +} + +ForwardInstData & +ForwardInstData::operator =(const ForwardInstData &src) +{ + numInsts = src.numInsts; + + for (unsigned int i = 0; i < src.numInsts; i++) + insts[i] = src.insts[i]; + + return *this; +} + +bool +ForwardInstData::isBubble() const +{ + return numInsts == 0 || insts[0]->isBubble(); +} + +void +ForwardInstData::bubbleFill() +{ + for (unsigned int i = 0; i < numInsts; i++) + insts[i] = MinorDynInst::bubble(); +} + +void +ForwardInstData::resize(unsigned int width) +{ + assert(width < MAX_FORWARD_INSTS); + numInsts = width; + + bubbleFill(); +} + +void +ForwardInstData::reportData(std::ostream &os) const +{ + if (isBubble()) { + os << '-'; + } else { + unsigned int i = 0; + + os << '('; + while (i != numInsts) { + insts[i]->reportData(os); + i++; + if (i != numInsts) + os << ','; + } + os << ')'; + } +} + +} diff --git a/src/cpu/minor/pipe_data.hh b/src/cpu/minor/pipe_data.hh new file mode 100644 index 000000000..4468cb89e --- /dev/null +++ b/src/cpu/minor/pipe_data.hh @@ -0,0 +1,288 @@ +/* + * Copyright (c) 2013-2014 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: Andrew Bardsley + */ + +/** + * @file + * + * Contains class definitions for data flowing between pipeline stages in + * the top-level structure portion of this model. Latch types are also + * defined which pair forward/backward flowing data specific to each stage + * pair. + * + * No post-configuration inter-stage communication should *ever* take place + * outside these classes (except for reservation!) + */ + +#ifndef __CPU_MINOR_PIPE_DATA_HH__ +#define __CPU_MINOR_PIPE_DATA_HH__ + +#include "cpu/minor/buffers.hh" +#include "cpu/minor/dyn_inst.hh" +#include "cpu/base.hh" + +namespace Minor +{ + +/** Forward data betwen Execute and Fetch1 carrying change-of-address/stream + * information. */ +class BranchData /* : public ReportIF, public BubbleIF */ +{ + public: + enum Reason + { + /* *** No change of stream (information to branch prediction) */ + + /* Don't branch at all (bubble) */ + NoBranch, + /* Don't branch, but here's the details of a correct prediction + * that was executed */ + CorrectlyPredictedBranch, + + /* *** Change of stream */ + + /* Take an unpredicted branch */ + UnpredictedBranch, + /* Take a branch on branch prediction data (from Fetch2) */ + BranchPrediction, + /* Prediction of wrong target PC */ + BadlyPredictedBranchTarget, + /* Bad branch prediction (didn't actually branch). Need to branch + * back to correct stream. If the target is wrong, use + * BadlyPredictedBranchTarget */ + BadlyPredictedBranch, + /* Suspend fetching for this thread (inst->id.threadId). + * This will be woken up by another stream changing branch so + * count it as stream changing itself and expect pc to be the PC + * of the next instruction */ + SuspendThread, + /* Wakeup fetching from Halted */ + WakeupFetch, + /* Branch from an interrupt (no instruction) */ + Interrupt, + /* Stop fetching in anticipation of of draining */ + HaltFetch + }; + + /** Is a request with this reason actually a request to change the + * PC rather than a bubble or branch prediction information */ + static bool isStreamChange(const BranchData::Reason reason); + + /** Is a request with this reason actually a 'real' branch, that is, + * a stream change that's not just an instruction to Fetch1 to halt + * or wake up */ + static bool isBranch(const BranchData::Reason reason); + + public: + /** Explanation for this branch */ + Reason reason; + + /** Sequence number of new stream/prediction to be adopted */ + InstSeqNum newStreamSeqNum; + InstSeqNum newPredictionSeqNum; + + /** Starting PC of that stream */ + TheISA::PCState target; + + /** Instruction which caused this branch */ + MinorDynInstPtr inst; + + public: + BranchData() : + reason(NoBranch), newStreamSeqNum(0), + newPredictionSeqNum(0), target(TheISA::PCState(0)), + inst(MinorDynInst::bubble()) + { } + + BranchData( + Reason reason_, + InstSeqNum new_stream_seq_num, + InstSeqNum new_prediction_seq_num, + TheISA::PCState target, + MinorDynInstPtr inst_) : + reason(reason_), + newStreamSeqNum(new_stream_seq_num), + newPredictionSeqNum(new_prediction_seq_num), + target(target), + inst(inst_) + { } + + /** BubbleIF interface */ + static BranchData bubble() { return BranchData(); } + bool isBubble() const { return reason == NoBranch; } + + /** As static isStreamChange but on this branch data */ + bool isStreamChange() const { return isStreamChange(reason); } + + /** As static isBranch but on this branch data */ + bool isBranch() const { return isBranch(reason); } + + /** ReportIF interface */ + void reportData(std::ostream &os) const; +}; + +/** Print a branch reason enum */ +std::ostream &operator <<(std::ostream &os, BranchData::Reason reason); + +/** Print BranchData contents in a format suitable for DPRINTF comments, not + * for MinorTrace */ +std::ostream &operator <<(std::ostream &os, const BranchData &branch); + +/** Line fetch data in the forward direction. Contains a single cache line + * (or fragment of a line), its address, a sequence number assigned when + * that line was fetched and a bubbleFlag that can allow ForwardLineData to + * be used to represent the absence of line data in a pipeline. */ +class ForwardLineData /* : public ReportIF, public BubbleIF */ +{ + private: + /** This line is a bubble. No other data member is required to be valid + * if this is true */ + bool bubbleFlag; + + public: + /** First byte address in the line. This is allowed to be + * <= pc.instAddr() */ + Addr lineBaseAddr; + + /** PC of the first requested inst within this line */ + TheISA::PCState pc; + + /** Explicit line width, don't rely on data.size */ + unsigned int lineWidth; + + public: + /** This line has a fault. The bubble flag will be false and seqNums + * will be valid but no data will */ + Fault fault; + + /** Thread, stream, prediction ... id of this line */ + InstId id; + + /** Line data. line[0] is the byte at address pc.instAddr(). Data is + * only valid upto lineWidth - 1. */ + uint8_t *line; + + /** Packet from which the line is taken */ + Packet *packet; + + public: + ForwardLineData() : + bubbleFlag(true), + lineBaseAddr(0), + lineWidth(0), + fault(NoFault), + line(NULL), + packet(NULL) + { + /* Make lines bubbles by default */ + } + + ~ForwardLineData() { line = NULL; } + + public: + /** This is a fault, not a line */ + bool isFault() const { return fault != NoFault; } + + /** Set fault and possible clear the bubble flag */ + void setFault(Fault fault_); + + /** In-place initialise a ForwardLineData, freeing and overridding the + * line */ + void allocateLine(unsigned int width_); + + /** Use the data from a packet as line instead of allocating new + * space. On destruction of this object, the packet will be destroyed */ + void adoptPacketData(Packet *packet); + + /** Free this ForwardLineData line. Note that these are shared between + * line objects and so you must be careful when deallocating them. + * Copying of ForwardLineData can, therefore, be done by default copy + * constructors/assignment */ + void freeLine(); + + /** BubbleIF interface */ + static ForwardLineData bubble() { return ForwardLineData(); } + bool isBubble() const { return bubbleFlag; } + + /** ReportIF interface */ + void reportData(std::ostream &os) const; +}; + +/** Maximum number of instructions that can be carried by the pipeline. */ +const unsigned int MAX_FORWARD_INSTS = 16; + +/** Forward flowing data between Fetch2,Decode,Execute carrying a packet of + * instructions of a width appropriate to the configured stage widths. + * Also carries exception information where instructions are not valid */ +class ForwardInstData /* : public ReportIF, public BubbleIF */ +{ + public: + /** Array of carried insts, ref counted */ + MinorDynInstPtr insts[MAX_FORWARD_INSTS]; + + /** The number of insts slots that can be expected to be valid insts */ + unsigned int numInsts; + + public: + explicit ForwardInstData(unsigned int width = 0); + + ForwardInstData(const ForwardInstData &src); + + public: + /** Number of instructions carried by this object */ + unsigned int width() const { return numInsts; } + + /** Copy the inst array only as far as numInsts */ + ForwardInstData &operator =(const ForwardInstData &src); + + /** Resize a bubble/empty ForwardInstData and fill with bubbles */ + void resize(unsigned int width); + + /** Fill with bubbles from 0 to width() - 1 */ + void bubbleFill(); + + /** BubbleIF interface */ + bool isBubble() const; + + /** ReportIF interface */ + void reportData(std::ostream &os) const; +}; + +} + +#endif /* __CPU_MINOR_PIPE_DATA_HH__ */ diff --git a/src/cpu/minor/pipeline.cc b/src/cpu/minor/pipeline.cc new file mode 100644 index 000000000..9d802234b --- /dev/null +++ b/src/cpu/minor/pipeline.cc @@ -0,0 +1,250 @@ +/* + * Copyright (c) 2013-2014 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: Andrew Bardsley + */ + +#include <algorithm> + +#include "cpu/minor/decode.hh" +#include "cpu/minor/execute.hh" +#include "cpu/minor/fetch1.hh" +#include "cpu/minor/fetch2.hh" +#include "cpu/minor/pipeline.hh" +#include "debug/Drain.hh" +#include "debug/MinorCPU.hh" +#include "debug/MinorTrace.hh" +#include "debug/Quiesce.hh" + +namespace Minor +{ + +Pipeline::Pipeline(MinorCPU &cpu_, MinorCPUParams ¶ms) : + Ticked(cpu_, &(cpu_.BaseCPU::numCycles)), + cpu(cpu_), + allow_idling(params.enableIdling), + f1ToF2(cpu.name() + ".f1ToF2", "lines", + params.fetch1ToFetch2ForwardDelay), + f2ToF1(cpu.name() + ".f2ToF1", "prediction", + params.fetch1ToFetch2BackwardDelay, true), + f2ToD(cpu.name() + ".f2ToD", "insts", + params.fetch2ToDecodeForwardDelay), + dToE(cpu.name() + ".dToE", "insts", + params.decodeToExecuteForwardDelay), + eToF1(cpu.name() + ".eToF1", "branch", + params.executeBranchDelay), + execute(cpu.name() + ".execute", cpu, params, + dToE.output(), eToF1.input()), + decode(cpu.name() + ".decode", cpu, params, + f2ToD.output(), dToE.input(), execute.inputBuffer), + fetch2(cpu.name() + ".fetch2", cpu, params, + f1ToF2.output(), eToF1.output(), f2ToF1.input(), f2ToD.input(), + decode.inputBuffer), + fetch1(cpu.name() + ".fetch1", cpu, params, + eToF1.output(), f1ToF2.input(), f2ToF1.output(), fetch2.inputBuffer), + activityRecorder(cpu.name() + ".activity", Num_StageId, + /* The max depth of inter-stage FIFOs */ + std::max(params.fetch1ToFetch2ForwardDelay, + std::max(params.fetch2ToDecodeForwardDelay, + std::max(params.decodeToExecuteForwardDelay, + params.executeBranchDelay)))), + needToSignalDrained(false) +{ + if (params.fetch1ToFetch2ForwardDelay < 1) { + fatal("%s: fetch1ToFetch2ForwardDelay must be >= 1 (%d)\n", + cpu.name(), params.fetch1ToFetch2ForwardDelay); + } + + if (params.fetch2ToDecodeForwardDelay < 1) { + fatal("%s: fetch2ToDecodeForwardDelay must be >= 1 (%d)\n", + cpu.name(), params.fetch2ToDecodeForwardDelay); + } + + if (params.decodeToExecuteForwardDelay < 1) { + fatal("%s: decodeToExecuteForwardDelay must be >= 1 (%d)\n", + cpu.name(), params.decodeToExecuteForwardDelay); + } + + if (params.executeBranchDelay < 1) { + fatal("%s: executeBranchDelay must be >= 1\n", + cpu.name(), params.executeBranchDelay); + } +} + +void +Pipeline::minorTrace() const +{ + fetch1.minorTrace(); + f1ToF2.minorTrace(); + f2ToF1.minorTrace(); + fetch2.minorTrace(); + f2ToD.minorTrace(); + decode.minorTrace(); + dToE.minorTrace(); + execute.minorTrace(); + eToF1.minorTrace(); + activityRecorder.minorTrace(); +} + +void +Pipeline::evaluate() +{ + /* Note that it's important to evaluate the stages in order to allow + * 'immediate', 0-time-offset TimeBuffer activity to be visible from + * later stages to earlier ones in the same cycle */ + execute.evaluate(); + decode.evaluate(); + fetch2.evaluate(); + fetch1.evaluate(); + + if (DTRACE(MinorTrace)) + minorTrace(); + + /* Update the time buffers after the stages */ + f1ToF2.evaluate(); + f2ToF1.evaluate(); + f2ToD.evaluate(); + dToE.evaluate(); + eToF1.evaluate(); + + /* The activity recorder must be be called after all the stages and + * before the idler (which acts on the advice of the activity recorder */ + activityRecorder.evaluate(); + + if (allow_idling) { + /* Become idle if we can but are not draining */ + if (!activityRecorder.active() && !needToSignalDrained) { + DPRINTF(Quiesce, "Suspending as the processor is idle\n"); + stop(); + } + + /* Deactivate all stages. Note that the stages *could* + * activate and deactivate themselves but that's fraught + * with additional difficulty. + * As organised herre */ + activityRecorder.deactivateStage(Pipeline::CPUStageId); + activityRecorder.deactivateStage(Pipeline::Fetch1StageId); + activityRecorder.deactivateStage(Pipeline::Fetch2StageId); + activityRecorder.deactivateStage(Pipeline::DecodeStageId); + activityRecorder.deactivateStage(Pipeline::ExecuteStageId); + } + + if (needToSignalDrained) /* Must be draining */ + { + DPRINTF(Drain, "Still draining\n"); + if (isDrained()) { + DPRINTF(Drain, "Signalling end of draining\n"); + cpu.signalDrainDone(); + needToSignalDrained = false; + stop(); + } + } +} + +MinorCPU::MinorCPUPort & +Pipeline::getInstPort() +{ + return fetch1.getIcachePort(); +} + +MinorCPU::MinorCPUPort & +Pipeline::getDataPort() +{ + return execute.getDcachePort(); +} + +void +Pipeline::wakeupFetch() +{ + execute.wakeupFetch(); +} + +unsigned int +Pipeline::drain(DrainManager *manager) +{ + DPRINTF(MinorCPU, "Draining pipeline by halting inst fetches. " + " Execution should drain naturally\n"); + + execute.drain(); + + /* Make sure that needToSignalDrained isn't accidentally set if we + * are 'pre-drained' */ + bool drained = isDrained(); + needToSignalDrained = !drained; + + return (drained ? 0 : 1); +} + +void +Pipeline::drainResume() +{ + DPRINTF(Drain, "Drain resume\n"); + execute.drainResume(); +} + +bool +Pipeline::isDrained() +{ + bool fetch1_drained = fetch1.isDrained(); + bool fetch2_drained = fetch2.isDrained(); + bool decode_drained = decode.isDrained(); + bool execute_drained = execute.isDrained(); + + bool f1_to_f2_drained = f1ToF2.empty(); + bool f2_to_f1_drained = f2ToF1.empty(); + bool f2_to_d_drained = f2ToD.empty(); + bool d_to_e_drained = dToE.empty(); + + bool ret = fetch1_drained && fetch2_drained && + decode_drained && execute_drained && + f1_to_f2_drained && f2_to_f1_drained && + f2_to_d_drained && d_to_e_drained; + + DPRINTF(MinorCPU, "Pipeline undrained stages state:%s%s%s%s%s%s%s%s\n", + (fetch1_drained ? "" : " Fetch1"), + (fetch2_drained ? "" : " Fetch2"), + (decode_drained ? "" : " Decode"), + (execute_drained ? "" : " Execute"), + (f1_to_f2_drained ? "" : " F1->F2"), + (f2_to_f1_drained ? "" : " F2->F1"), + (f2_to_d_drained ? "" : " F2->D"), + (d_to_e_drained ? "" : " D->E") + ); + + return ret; +} + +} diff --git a/src/cpu/minor/pipeline.hh b/src/cpu/minor/pipeline.hh new file mode 100644 index 000000000..893efbf50 --- /dev/null +++ b/src/cpu/minor/pipeline.hh @@ -0,0 +1,145 @@ +/* + * Copyright (c) 2013-2014 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: Andrew Bardsley + */ + +/** + * @file + * + * The constructed pipeline. Kept out of MinorCPU to keep the interface + * between the CPU and its grubby implementation details clean. + */ + +#ifndef __CPU_MINOR_PIPELINE_HH__ +#define __CPU_MINOR_PIPELINE_HH__ + +#include "cpu/minor/activity.hh" +#include "cpu/minor/cpu.hh" +#include "cpu/minor/decode.hh" +#include "cpu/minor/execute.hh" +#include "cpu/minor/fetch1.hh" +#include "cpu/minor/fetch2.hh" +#include "params/MinorCPU.hh" +#include "sim/ticked_object.hh" + +namespace Minor +{ + +/** + * @namespace Minor + * + * Minor contains all the definitions within the MinorCPU apart from the CPU + * class itself + */ + +/** The constructed pipeline. Kept out of MinorCPU to keep the interface + * between the CPU and its grubby implementation details clean. */ +class Pipeline : public Ticked +{ + protected: + MinorCPU &cpu; + + /** Allow cycles to be skipped when the pipeline is idle */ + bool allow_idling; + + Latch<ForwardLineData> f1ToF2; + Latch<BranchData> f2ToF1; + Latch<ForwardInstData> f2ToD; + Latch<ForwardInstData> dToE; + Latch<BranchData> eToF1; + + Execute execute; + Decode decode; + Fetch2 fetch2; + Fetch1 fetch1; + + /** Activity recording for the pipeline. This is access through the CPU + * by the pipeline stages but belongs to the Pipeline as it is the + * cleanest place to initialise it */ + MinorActivityRecorder activityRecorder; + + public: + /** Enumerated ids of the 'stages' for the activity recorder */ + enum StageId + { + /* A stage representing wakeup of the whole processor */ + CPUStageId = 0, + /* Real pipeline stages */ + Fetch1StageId, Fetch2StageId, DecodeStageId, ExecuteStageId, + Num_StageId /* Stage count */ + }; + + /** True after drain is called but draining isn't complete */ + bool needToSignalDrained; + + public: + Pipeline(MinorCPU &cpu_, MinorCPUParams ¶ms); + + public: + /** Wake up the Fetch unit. This is needed on thread activation esp. + * after quiesce wakeup */ + void wakeupFetch(); + + /** Try to drain the CPU */ + unsigned int drain(DrainManager *manager); + + void drainResume(); + + /** Test to see if the CPU is drained */ + bool isDrained(); + + /** A custom evaluate allows report in the right place (between + * stages and pipeline advance) */ + void evaluate(); + + void minorTrace() const; + + /** Functions below here are BaseCPU operations passed on to pipeline + * stages */ + + /** Return the IcachePort belonging to Fetch1 for the CPU */ + MinorCPU::MinorCPUPort &getInstPort(); + /** Return the DcachePort belonging to Execute for the CPU */ + MinorCPU::MinorCPUPort &getDataPort(); + + /** To give the activity recorder to the CPU */ + MinorActivityRecorder *getActivityRecorder() { return &activityRecorder; } +}; + +} + +#endif /* __CPU_MINOR_PIPELINE_HH__ */ diff --git a/src/cpu/minor/scoreboard.cc b/src/cpu/minor/scoreboard.cc new file mode 100644 index 000000000..f6b1f7944 --- /dev/null +++ b/src/cpu/minor/scoreboard.cc @@ -0,0 +1,323 @@ +/* + * Copyright (c) 2013-2014 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: Andrew Bardsley + */ + +#include "arch/registers.hh" +#include "cpu/minor/scoreboard.hh" +#include "cpu/reg_class.hh" +#include "debug/MinorScoreboard.hh" +#include "debug/MinorTiming.hh" + +namespace Minor +{ + +bool +Scoreboard::findIndex(RegIndex reg, Index &scoreboard_index) +{ + RegClass reg_class = regIdxToClass(reg); + bool ret = false; + + if (reg == TheISA::ZeroReg) { + /* Don't bother with the zero register */ + ret = false; + } else { + switch (reg_class) + { + case IntRegClass: + scoreboard_index = reg; + ret = true; + break; + case FloatRegClass: + scoreboard_index = TheISA::NumIntRegs + TheISA::NumCCRegs + + reg - TheISA::FP_Reg_Base; + ret = true; + break; + case CCRegClass: + scoreboard_index = TheISA::NumIntRegs + reg - TheISA::FP_Reg_Base; + ret = true; + break; + case MiscRegClass: + /* Don't bother with Misc registers */ + ret = false; + break; + } + } + + return ret; +} + +/** Flatten a RegIndex, irrespective of what reg type it's pointing to */ +static TheISA::RegIndex +flattenRegIndex(TheISA::RegIndex reg, ThreadContext *thread_context) +{ + RegClass reg_class = regIdxToClass(reg); + TheISA::RegIndex ret = reg; + + switch (reg_class) + { + case IntRegClass: + ret = thread_context->flattenIntIndex(reg); + break; + case FloatRegClass: + ret = thread_context->flattenFloatIndex(reg); + break; + case CCRegClass: + ret = thread_context->flattenCCIndex(reg); + break; + case MiscRegClass: + /* Don't bother to flatten misc regs as we don't need them here */ + /* return thread_context->flattenMiscIndex(reg); */ + ret = reg; + break; + } + + return ret; +} + +void +Scoreboard::markupInstDests(MinorDynInstPtr inst, Cycles retire_time, + ThreadContext *thread_context, bool mark_unpredictable) +{ + if (inst->isFault()) + return; + + StaticInstPtr staticInst = inst->staticInst; + unsigned int num_dests = staticInst->numDestRegs(); + + /** Mark each destination register */ + for (unsigned int dest_index = 0; dest_index < num_dests; + dest_index++) + { + RegIndex reg = flattenRegIndex( + staticInst->destRegIdx(dest_index), thread_context); + Index index; + + if (findIndex(reg, index)) { + if (mark_unpredictable) + numUnpredictableResults[index]++; + + inst->flatDestRegIdx[dest_index] = reg; + + numResults[index]++; + returnCycle[index] = retire_time; + /* We should be able to rely on only being given accending + * execSeqNums, but sanity check */ + if (inst->id.execSeqNum > writingInst[index]) { + writingInst[index] = inst->id.execSeqNum; + fuIndices[index] = inst->fuIndex; + } + + DPRINTF(MinorScoreboard, "Marking up inst: %s" + " regIndex: %d final numResults: %d returnCycle: %d\n", + *inst, index, numResults[index], returnCycle[index]); + } else { + /* Use ZeroReg to mark invalid/untracked dests */ + inst->flatDestRegIdx[dest_index] = TheISA::ZeroReg; + } + } +} + +InstSeqNum +Scoreboard::execSeqNumToWaitFor(MinorDynInstPtr inst, + ThreadContext *thread_context) +{ + InstSeqNum ret = 0; + + if (inst->isFault()) + return ret; + + StaticInstPtr staticInst = inst->staticInst; + unsigned int num_srcs = staticInst->numSrcRegs(); + + for (unsigned int src_index = 0; src_index < num_srcs; src_index++) { + RegIndex reg = flattenRegIndex(staticInst->srcRegIdx(src_index), + thread_context); + unsigned short int index; + + if (findIndex(reg, index)) { + if (writingInst[index] > ret) + ret = writingInst[index]; + } + } + + DPRINTF(MinorScoreboard, "Inst: %s depends on execSeqNum: %d\n", + *inst, ret); + + return ret; +} + +void +Scoreboard::clearInstDests(MinorDynInstPtr inst, bool clear_unpredictable) +{ + if (inst->isFault()) + return; + + StaticInstPtr staticInst = inst->staticInst; + unsigned int num_dests = staticInst->numDestRegs(); + + /** Mark each destination register */ + for (unsigned int dest_index = 0; dest_index < num_dests; + dest_index++) + { + RegIndex reg = inst->flatDestRegIdx[dest_index]; + Index index; + + if (findIndex(reg, index)) { + if (clear_unpredictable && numUnpredictableResults[index] != 0) + numUnpredictableResults[index] --; + + numResults[index] --; + + if (numResults[index] == 0) { + returnCycle[index] = Cycles(0); + writingInst[index] = 0; + fuIndices[index] = -1; + } + + DPRINTF(MinorScoreboard, "Clearing inst: %s" + " regIndex: %d final numResults: %d\n", + *inst, index, numResults[index]); + } + } +} + +bool +Scoreboard::canInstIssue(MinorDynInstPtr inst, + const std::vector<Cycles> *src_reg_relative_latencies, + const std::vector<bool> *cant_forward_from_fu_indices, + Cycles now, ThreadContext *thread_context) +{ + /* Always allow fault to be issued */ + if (inst->isFault()) + return true; + + StaticInstPtr staticInst = inst->staticInst; + unsigned int num_srcs = staticInst->numSrcRegs(); + + /* Default to saying you can issue */ + bool ret = true; + + unsigned int num_relative_latencies = 0; + Cycles default_relative_latency = Cycles(0); + + /* Where relative latencies are given, the default is the last + * one as that allows the rel. lat. list to be shorted than the + * number of src. regs */ + if (src_reg_relative_latencies && + src_reg_relative_latencies->size() != 0) + { + num_relative_latencies = src_reg_relative_latencies->size(); + default_relative_latency = (*src_reg_relative_latencies) + [num_relative_latencies-1]; + } + + /* For each source register, find the latest result */ + unsigned int src_index = 0; + while (src_index < num_srcs && /* More registers */ + ret /* Still possible */) + { + RegIndex reg = flattenRegIndex(staticInst->srcRegIdx(src_index), + thread_context); + unsigned short int index; + + if (findIndex(reg, index)) { + bool cant_forward = fuIndices[index] != 1 && + cant_forward_from_fu_indices && + index < cant_forward_from_fu_indices->size() && + (*cant_forward_from_fu_indices)[index]; + + Cycles relative_latency = (cant_forward ? Cycles(0) : + (src_index >= num_relative_latencies ? + default_relative_latency : + (*src_reg_relative_latencies)[src_index])); + + if (returnCycle[index] > (now + relative_latency) || + numUnpredictableResults[index] != 0) + { + ret = false; + } + } + src_index++; + } + + if (DTRACE(MinorTiming)) { + if (ret && num_srcs > num_relative_latencies && + num_relative_latencies != 0) + { + DPRINTF(MinorTiming, "Warning, inst: %s timing extra decode has" + " more src. regs: %d than relative latencies: %d\n", + staticInst->disassemble(0), num_srcs, num_relative_latencies); + } + } + + return ret; +} + +void +Scoreboard::minorTrace() const +{ + std::ostringstream result_stream; + + bool printed_element = false; + + unsigned int i = 0; + while (i < numRegs) { + unsigned short int num_results = numResults[i]; + unsigned short int num_unpredictable_results = + numUnpredictableResults[i]; + + if (!(num_results == 0 && num_unpredictable_results == Cycles(0))) { + if (printed_element) + result_stream << ','; + + result_stream << '(' << i << ',' + << num_results << '/' + << num_unpredictable_results << '/' + << returnCycle[i] << '/' + << writingInst[i] << ')'; + + printed_element = true; + } + + i++; + } + + MINORTRACE("busy=%s\n", result_stream.str()); +} + +} diff --git a/src/cpu/minor/scoreboard.hh b/src/cpu/minor/scoreboard.hh new file mode 100644 index 000000000..711bcafb2 --- /dev/null +++ b/src/cpu/minor/scoreboard.hh @@ -0,0 +1,145 @@ +/* + * Copyright (c) 2013-2014 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: Andrew Bardsley + */ + +/** + * @file + * + * A simple instruction scoreboard for tracking dependencies in Execute. + */ + +#ifndef __CPU_MINOR_SCOREBOARD_HH__ +#define __CPU_MINOR_SCOREBOARD_HH__ + +#include "cpu/minor/cpu.hh" +#include "cpu/minor/dyn_inst.hh" +#include "cpu/minor/trace.hh" + +namespace Minor +{ + +/** A scoreboard of register dependencies including, for each register: + * The number of in-flight instructions which will generate a result for + * this register */ +class Scoreboard : public Named +{ + public: + /** The number of registers in the Scoreboard. These + * are just the integer, CC and float registers packed + * together with integer regs in the range [0,NumIntRegs-1], + * CC regs in the range [NumIntRegs, NumIntRegs+NumCCRegs-1] + * and float regs in the range + * [NumIntRegs+NumCCRegs, NumFloatRegs+NumIntRegs+NumCCRegs-1] */ + const unsigned numRegs; + + /** Type to use for thread context registers */ + typedef TheISA::RegIndex RegIndex; + + /** Type to use when indexing numResults */ + typedef unsigned short int Index; + + /** Count of the number of in-flight instructions that + * have results for each register */ + std::vector<Index> numResults; + + /** Count of the number of results which can't be predicted */ + std::vector<Index> numUnpredictableResults; + + /** Index of the FU generating this result */ + std::vector<int> fuIndices; + + /** The estimated cycle number that the result will be presented. + * This can be offset from to allow forwarding to be simulated as + * long as instruction completion is *strictly* in order with + * respect to instructions with unpredictable result timing */ + std::vector<Cycles> returnCycle; + + /** The execute sequence number of the most recent inst to generate this + * register value */ + std::vector<InstSeqNum> writingInst; + + public: + Scoreboard(const std::string &name) : + Named(name), + numRegs(TheISA::NumIntRegs + TheISA::NumCCRegs + + TheISA::NumFloatRegs), + numResults(numRegs, 0), + numUnpredictableResults(numRegs, 0), + fuIndices(numRegs, 0), + returnCycle(numRegs, Cycles(0)), + writingInst(numRegs, 0) + { } + + public: + /** Sets scoreboard_index to the index into numResults of the + * given register index. Returns true if the given register + * is in the scoreboard and false if it isn't */ + bool findIndex(RegIndex reg, Index &scoreboard_index); + + /** Mark up an instruction's effects by incrementing + * numResults counts. If mark_unpredictable is true, the inst's + * destination registers are marked as being unpredictable without + * an estimated retire time */ + void markupInstDests(MinorDynInstPtr inst, Cycles retire_time, + ThreadContext *thread_context, bool mark_unpredictable); + + /** Clear down the dependencies for this instruction. clear_unpredictable + * must match mark_unpredictable for the same inst. */ + void clearInstDests(MinorDynInstPtr inst, bool clear_unpredictable); + + /** Returns the exec sequence number of the most recent inst on + * which the given inst depends. Useful for determining which + * inst must actually be committed before a dependent inst + * can call initiateAcc */ + InstSeqNum execSeqNumToWaitFor(MinorDynInstPtr inst, + ThreadContext *thread_context); + + /** Can this instruction be issued. Are any of its source registers + * due to be written by other marked-up instructions in flight */ + bool canInstIssue(MinorDynInstPtr inst, + const std::vector<Cycles> *src_reg_relative_latencies, + const std::vector<bool> *cant_forward_from_fu_indices, + Cycles now, ThreadContext *thread_context); + + /** MinorTraceIF interface */ + void minorTrace() const; +}; + +} + +#endif /* __CPU_MINOR_SCOREBOARD_HH__ */ diff --git a/src/cpu/minor/stats.cc b/src/cpu/minor/stats.cc new file mode 100644 index 000000000..baa0aa7f3 --- /dev/null +++ b/src/cpu/minor/stats.cc @@ -0,0 +1,87 @@ +/* + * Copyright (c) 2012-2014 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: Andrew Bardsley + */ + +#include "cpu/minor/stats.hh" + +namespace Minor +{ + +MinorStats::MinorStats() +{ } + +void +MinorStats::regStats(const std::string &name, BaseCPU &baseCpu) +{ + numInsts + .name(name + ".committedInsts") + .desc("Number of instructions committed"); + + numOps + .name(name + ".committedOps") + .desc("Number of ops (including micro ops) committed"); + + numDiscardedOps + .name(name + ".discardedOps") + .desc("Number of ops (including micro ops) which were discarded " + "before commit"); + + numFetchSuspends + .name(name + ".numFetchSuspends") + .desc("Number of times Execute suspended instruction fetching"); + + quiesceCycles + .name(name + ".quiesceCycles") + .desc("Total number of cycles that CPU has spent quiesced or waiting " + "for an interrupt") + .prereq(quiesceCycles); + + cpi + .name(name + ".cpi") + .desc("CPI: cycles per instruction") + .precision(6); + cpi = baseCpu.numCycles / numInsts; + + ipc + .name(name + ".ipc") + .desc("IPC: instructions per cycle") + .precision(6); + ipc = numInsts / baseCpu.numCycles; +} + +}; diff --git a/src/cpu/minor/stats.hh b/src/cpu/minor/stats.hh new file mode 100644 index 000000000..dc246304d --- /dev/null +++ b/src/cpu/minor/stats.hh @@ -0,0 +1,88 @@ +/* + * Copyright (c) 2011-2014 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: Andrew Bardsley + */ + +/** + * @file + * + * The stats for MinorCPU separated from the CPU definition. + */ + +#ifndef __CPU_MINOR_STATS_HH__ +#define __CPU_MINOR_STATS_HH__ + +#include "base/statistics.hh" +#include "cpu/base.hh" +#include "sim/ticked_object.hh" + +namespace Minor +{ + +/** Currently unused stats class. */ +class MinorStats +{ + public: + /** Number of simulated instructions */ + Stats::Scalar numInsts; + + /** Number of simulated insts and microops */ + Stats::Scalar numOps; + + /** Number of ops discarded before committing */ + Stats::Scalar numDiscardedOps; + + /** Number of times fetch was asked to suspend by Execute */ + Stats::Scalar numFetchSuspends; + + /** Number of cycles in quiescent state */ + Stats::Scalar quiesceCycles; + + /** CPI/IPC for total cycle counts and macro insts */ + Stats::Formula cpi; + Stats::Formula ipc; + + public: + MinorStats(); + + public: + void regStats(const std::string &name, BaseCPU &baseCpu); +}; + +} + +#endif /* __CPU_MINOR_STATS_HH__ */ diff --git a/src/cpu/minor/trace.hh b/src/cpu/minor/trace.hh new file mode 100644 index 000000000..9bbe09750 --- /dev/null +++ b/src/cpu/minor/trace.hh @@ -0,0 +1,75 @@ +/* + * Copyright (c) 2013-2014 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: Andrew Bardsley + */ + +/** + * @file + * + * This file contains miscellaneous classes and functions for formatting + * general trace information and also MinorTrace information. + * + * MinorTrace is this model's cycle-by-cycle trace information for use by + * minorview. + */ + +#ifndef __CPU_MINOR_TRACE_HH__ +#define __CPU_MINOR_TRACE_HH__ + +#include <string> + +#include "base/trace.hh" +#include "debug/MinorTrace.hh" + +namespace Minor +{ + +/** DPRINTFN for MinorTrace reporting */ +#define MINORTRACE(...) \ + DPRINTF(MinorTrace, "MinorTrace: " __VA_ARGS__) + +/** DPRINTFN for MinorTrace MinorInst line reporting */ +#define MINORINST(sim_object, ...) \ + DPRINTFS(MinorTrace, (sim_object), "MinorInst: " __VA_ARGS__) + +/** DPRINTFN for MinorTrace MinorLine line reporting */ +#define MINORLINE(sim_object, ...) \ + DPRINTFS(MinorTrace, (sim_object), "MinorLine: " __VA_ARGS__) + +} + +#endif /* __CPU_MINOR_TRACE_HH__ */ diff --git a/src/cpu/pred/SConscript b/src/cpu/pred/SConscript index 5b2ecceef..bb9342f06 100644 --- a/src/cpu/pred/SConscript +++ b/src/cpu/pred/SConscript @@ -30,7 +30,8 @@ Import('*') -if 'InOrderCPU' in env['CPU_MODELS'] or 'O3CPU' in env['CPU_MODELS']: +if 'InOrderCPU' in env['CPU_MODELS'] or 'O3CPU' in env['CPU_MODELS'] \ + or 'Minor' in env['CPU_MODELS']: SimObject('BranchPredictor.py') Source('bpred_unit.cc') diff --git a/src/cpu/static_inst.hh b/src/cpu/static_inst.hh index f598c920d..375b7d0ba 100644 --- a/src/cpu/static_inst.hh +++ b/src/cpu/static_inst.hh @@ -59,6 +59,11 @@ class CheckerCPU; class AtomicSimpleCPU; class TimingSimpleCPU; class InorderCPU; +namespace Minor +{ + class ExecContext; +}; + class SymbolTable; namespace Trace { diff --git a/src/cpu/timing_expr.cc b/src/cpu/timing_expr.cc new file mode 100644 index 000000000..d6d904956 --- /dev/null +++ b/src/cpu/timing_expr.cc @@ -0,0 +1,248 @@ +/* + * Copyright (c) 2013-2014 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: Andrew Bardsley + */ + +#include "base/intmath.hh" +#include "cpu/timing_expr.hh" + +TimingExprEvalContext::TimingExprEvalContext (StaticInstPtr inst_, + ThreadContext *thread_, + TimingExprLet *let_) : + inst(inst_), thread(thread_), let(let_) +{ + /* Reserve space to hold the results of evaluating the + * let expressions */ + if (let) { + unsigned int num_defns = let->defns.size(); + + results.resize(num_defns, 0); + resultAvailable.resize(num_defns, false); + } +} + +uint64_t TimingExprSrcReg::eval(TimingExprEvalContext &context) +{ + return context.inst->srcRegIdx(index); +} + +uint64_t TimingExprReadIntReg::eval(TimingExprEvalContext &context) +{ + return context.thread->readIntReg(reg->eval(context)); +} + +uint64_t TimingExprLet::eval(TimingExprEvalContext &context) +{ + TimingExprEvalContext new_context(context.inst, + context.thread, this); + + return expr->eval(new_context); +} + +uint64_t TimingExprRef::eval(TimingExprEvalContext &context) +{ + /* Lookup the result, evaluating if necessary. @todo, this + * should have more error checking */ + if (!context.resultAvailable[index]) { + context.results[index] = context.let->defns[index]->eval(context); + context.resultAvailable[index] = true; + } + + return context.results[index]; +} + +uint64_t TimingExprUn::eval(TimingExprEvalContext &context) +{ + uint64_t arg_value = arg->eval(context); + uint64_t ret = 0; + + switch (op) { + case Enums::timingExprSizeInBits: + if (arg_value == 0) + ret = 0; + else + ret = ceilLog2(arg_value); + break; + case Enums::timingExprNot: + ret = arg_value != 0; + break; + case Enums::timingExprInvert: + ret = ~arg_value; + break; + case Enums::timingExprSignExtend32To64: + ret = static_cast<int64_t>( + static_cast<int32_t>(arg_value)); + break; + case Enums::timingExprAbs: + if (static_cast<int64_t>(arg_value) < 0) + ret = -arg_value; + else + ret = arg_value; + break; + default: + break; + } + + return ret; +} + +uint64_t TimingExprBin::eval(TimingExprEvalContext &context) +{ + uint64_t left_value = left->eval(context); + uint64_t right_value = right->eval(context); + uint64_t ret = 0; + + switch (op) { + case Enums::timingExprAdd: + ret = left_value + right_value; + break; + case Enums::timingExprSub: + ret = left_value - right_value; + break; + case Enums::timingExprUMul: + ret = left_value * right_value; + break; + case Enums::timingExprUDiv: + if (right_value != 0) { + ret = left_value / right_value; + } + break; + case Enums::timingExprUCeilDiv: + if (right_value != 0) { + ret = (left_value + (right_value - 1)) / right_value; + } + break; + case Enums::timingExprSMul: + ret = static_cast<int64_t>(left_value) * + static_cast<int64_t>(right_value); + break; + case Enums::timingExprSDiv: + if (right_value != 0) { + ret = static_cast<int64_t>(left_value) / + static_cast<int64_t>(right_value); + } + break; + case Enums::timingExprEqual: + ret = left_value == right_value; + break; + case Enums::timingExprNotEqual: + ret = left_value != right_value; + break; + case Enums::timingExprULessThan: + ret = left_value < right_value; + break; + case Enums::timingExprUGreaterThan: + ret = left_value > right_value; + break; + case Enums::timingExprSLessThan: + ret = static_cast<int64_t>(left_value) < + static_cast<int64_t>(right_value); + break; + case Enums::timingExprSGreaterThan: + ret = static_cast<int64_t>(left_value) > + static_cast<int64_t>(right_value); + break; + case Enums::timingExprAnd: + ret = (left_value != 0) && (right_value != 0); + break; + case Enums::timingExprOr: + ret = (left_value != 0) || (right_value != 0); + break; + default: + break; + } + + return ret; +} + +uint64_t TimingExprIf::eval(TimingExprEvalContext &context) +{ + uint64_t cond_value = cond->eval(context); + + if (cond_value != 0) + return trueExpr->eval(context); + else + return falseExpr->eval(context); +} + +TimingExprLiteral * +TimingExprLiteralParams::create() +{ + return new TimingExprLiteral(this); +} + +TimingExprSrcReg * +TimingExprSrcRegParams::create() +{ + return new TimingExprSrcReg(this); +} + +TimingExprReadIntReg * +TimingExprReadIntRegParams::create() +{ + return new TimingExprReadIntReg(this); +} + +TimingExprLet * +TimingExprLetParams::create() +{ + return new TimingExprLet(this); +} + +TimingExprRef * +TimingExprRefParams::create() +{ + return new TimingExprRef(this); +} + +TimingExprUn * +TimingExprUnParams::create() +{ + return new TimingExprUn(this); +} + +TimingExprBin * +TimingExprBinParams::create() +{ + return new TimingExprBin(this); +} + +TimingExprIf * +TimingExprIfParams::create() +{ + return new TimingExprIf(this); +} diff --git a/src/cpu/timing_expr.hh b/src/cpu/timing_expr.hh new file mode 100644 index 000000000..d2c38ea90 --- /dev/null +++ b/src/cpu/timing_expr.hh @@ -0,0 +1,216 @@ +/* + * Copyright (c) 2013-2014 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: Andrew Bardsley + */ + +/* + * These classes define an expression language over uint64_t with only + * a few operators. This can be used to form expressions for the extra + * delay required in variable execution time instructions. + * + * Expressions, in evaluation, will have access to the ThreadContext and + * a StaticInst. + */ + +#ifndef __CPU_TIMING_EXPR_HH__ +#define __CPU_TIMING_EXPR_HH__ + +#include "cpu/static_inst.hh" +#include "cpu/thread_context.hh" +#include "enums/TimingExprOp.hh" +#include "params/TimingExpr.hh" +#include "params/TimingExprBin.hh" +#include "params/TimingExprIf.hh" +#include "params/TimingExprLet.hh" +#include "params/TimingExprLiteral.hh" +#include "params/TimingExprReadIntReg.hh" +#include "params/TimingExprRef.hh" +#include "params/TimingExprSrcReg.hh" +#include "params/TimingExprUn.hh" +#include "sim/sim_object.hh" + +/** These classes are just the C++ counterparts for those in Expr.py and + * are, therefore, documented there */ + +class TimingExprLet; + +/** Object to gather the visible context for evaluation */ +class TimingExprEvalContext +{ + public: + /** Special visible context */ + StaticInstPtr inst; + ThreadContext *thread; + + /** Context visible as sub expressions. results will hold the results + * of (lazily) evaluating let's expressions. resultAvailable elements + * are true when a result has actually been evaluated */ + TimingExprLet *let; + std::vector<uint64_t> results; + std::vector<bool > resultAvailable; + + TimingExprEvalContext(StaticInstPtr inst_, + ThreadContext *thread_, TimingExprLet *let_); +}; + +class TimingExpr : public SimObject +{ + public: + TimingExpr(const TimingExprParams *params) : + SimObject(params) + { } + + virtual uint64_t eval(TimingExprEvalContext &context) = 0; +}; + +class TimingExprLiteral : public TimingExpr +{ + public: + uint64_t value; + + TimingExprLiteral(const TimingExprLiteralParams *params) : + TimingExpr(params), + value(params->value) + { } + + uint64_t eval(TimingExprEvalContext &context) { return value; } +}; + +class TimingExprSrcReg : public TimingExpr +{ + public: + unsigned int index; + + TimingExprSrcReg(const TimingExprSrcRegParams *params) : + TimingExpr(params), + index(params->index) + { } + + uint64_t eval(TimingExprEvalContext &context); +}; + +class TimingExprReadIntReg : public TimingExpr +{ + public: + TimingExpr *reg; + + TimingExprReadIntReg(const TimingExprReadIntRegParams *params) : + TimingExpr(params), + reg(params->reg) + { } + + uint64_t eval(TimingExprEvalContext &context); +}; + +class TimingExprLet : public TimingExpr +{ + public: + std::vector<TimingExpr *> defns; + TimingExpr *expr; + + TimingExprLet(const TimingExprLetParams *params) : + TimingExpr(params), + defns(params->defns), + expr(params->expr) + { } + + uint64_t eval(TimingExprEvalContext &context); +}; + +class TimingExprRef : public TimingExpr +{ + public: + unsigned int index; + + TimingExprRef(const TimingExprRefParams *params) : + TimingExpr(params), + index(params->index) + { } + + uint64_t eval(TimingExprEvalContext &context); +}; + +class TimingExprUn : public TimingExpr +{ + public: + Enums::TimingExprOp op; + TimingExpr *arg; + + TimingExprUn(const TimingExprUnParams *params) : + TimingExpr(params), + op(params->op), + arg(params->arg) + { } + + uint64_t eval(TimingExprEvalContext &context); +}; + +class TimingExprBin : public TimingExpr +{ + public: + Enums::TimingExprOp op; + TimingExpr *left; + TimingExpr *right; + + TimingExprBin(const TimingExprBinParams *params) : + TimingExpr(params), + op(params->op), + left(params->left), + right(params->right) + { } + + uint64_t eval(TimingExprEvalContext &context); +}; + +class TimingExprIf : public TimingExpr +{ + public: + TimingExpr *cond; + TimingExpr *trueExpr; + TimingExpr *falseExpr; + + TimingExprIf(const TimingExprIfParams *params) : + TimingExpr(params), + cond(params->cond), + trueExpr(params->trueExpr), + falseExpr(params->falseExpr) + { } + + uint64_t eval(TimingExprEvalContext &context); +}; + +#endif diff --git a/src/doc/inside-minor.doxygen b/src/doc/inside-minor.doxygen new file mode 100644 index 000000000..e55f61c01 --- /dev/null +++ b/src/doc/inside-minor.doxygen @@ -0,0 +1,1091 @@ +# Copyright (c) 2014 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: Andrew Bardsley + +namespace Minor +{ + +/*! + +\page minor Inside the Minor CPU model + +\tableofcontents + +This document contains a description of the structure and function of the +Minor gem5 in-order processor model. It is recommended reading for anyone who +wants to understand Minor's internal organisation, design decisions, C++ +implementation and Python configuration. A familiarity with gem5 and some of +its internal structures is assumed. This document is meant to be read +alongside the Minor source code and to explain its general structure without +being too slavish about naming every function and data type. + +\section whatis What is Minor? + +Minor is an in-order processor model with a fixed pipeline but configurable +data structures and execute behaviour. It is intended to be used to model +processors with strict in-order execution behaviour and allows visualisation +of an instruction's position in the pipeline through the +MinorTrace/minorview.py format/tool. The intention is to provide a framework +for micro-architecturally correlating the model with a particular, chosen +processor with similar capabilities. + +\section philo Design philosophy + +\subsection mt Multithreading + +The model isn't currently capable of multithreading but there are THREAD +comments in key places where stage data needs to be arrayed to support +multithreading. + +\subsection structs Data structures + +Decorating data structures with large amounts of life-cycle information is +avoided. Only instructions (MinorDynInst) contain a significant proportion of +their data content whose values are not set at construction. + +All internal structures have fixed sizes on construction. Data held in queues +and FIFOs (MinorBuffer, FUPipeline) should have a BubbleIF interface to +allow a distinct 'bubble'/no data value option for each type. + +Inter-stage 'struct' data is packaged in structures which are passed by value. +Only MinorDynInst, the line data in ForwardLineData and the memory-interfacing +objects Fetch1::FetchRequest and LSQ::LSQRequest are '::new' allocated while +running the model. + +\section model Model structure + +Objects of class MinorCPU are provided by the model to gem5. MinorCPU +implements the interfaces of (cpu.hh) and can provide data and +instruction interfaces for connection to a cache system. The model is +configured in a similar way to other gem5 models through Python. That +configuration is passed on to MinorCPU::pipeline (of class Pipeline) which +actually implements the processor pipeline. + +The hierarchy of major unit ownership from MinorCPU down looks like this: + +<ul> +<li>MinorCPU</li> +<ul> + <li>Pipeline - container for the pipeline, owns the cyclic 'tick' + event mechanism and the idling (cycle skipping) mechanism.</li> + <ul> + <li>Fetch1 - instruction fetch unit responsible for fetching cache + lines (or parts of lines from the I-cache interface)</li> + <ul> + <li>Fetch1::IcachePort - interface to the I-cache from + Fetch1</li> + </ul> + <li>Fetch2 - line to instruction decomposition</li> + <li>Decode - instruction to micro-op decomposition</li> + <li>Execute - instruction execution and data memory + interface</li> + <ul> + <li>LSQ - load store queue for memory ref. instructions</li> + <li>LSQ::DcachePort - interface to the D-cache from + Execute</li> + </ul> + </ul> + </ul> +</ul> + +\section keystruct Key data structures + +\subsection ids Instruction and line identity: InstId (dyn_inst.hh) + +An InstId contains the sequence numbers and thread numbers that describe the +life cycle and instruction stream affiliations of individual fetched cache +lines and instructions. + +An InstId is printed in one of the following forms: + + - T/S.P/L - for fetched cache lines + - T/S.P/L/F - for instructions before Decode + - T/S.P/L/F.E - for instructions from Decode onwards + +for example: + + - 0/10.12/5/6.7 + +InstId's fields are: + +<table> +<tr> + <td><b>Field</b></td> + <td><b>Symbol</b></td> + <td><b>Generated by</b></td> + <td><b>Checked by</b></td> + <td><b>Function</b></td> +</tr> + +<tr> + <td>InstId::threadId</td> + <td>T</td> + <td>Fetch1</td> + <td>Everywhere the thread number is needed</td> + <td>Thread number (currently always 0).</td> +</tr> + +<tr> + <td>InstId::streamSeqNum</td> + <td>S</td> + <td>Execute</td> + <td>Fetch1, Fetch2, Execute (to discard lines/insts)</td> + <td>Stream sequence number as chosen by Execute. Stream + sequence numbers change after changes of PC (branches, exceptions) in + Execute and are used to separate pre and post branch instruction + streams.</td> +</tr> + +<tr> + <td>InstId::predictionSeqNum</td> + <td>P</td> + <td>Fetch2</td> + <td>Fetch2 (while discarding lines after prediction)</td> + <td>Prediction sequence numbers represent branch prediction decisions. + This is used by Fetch2 to mark lines/instructions according to the last + followed branch prediction made by Fetch2. Fetch2 can signal to Fetch1 + that it should change its fetch address and mark lines with a new + prediction sequence number (which it will only do if the stream sequence + number Fetch1 expects matches that of the request). </td> </tr> + +<tr> +<td>InstId::lineSeqNum</td> +<td>L</td> +<td>Fetch1</td> +<td>(Just for debugging)</td> +<td>Line fetch sequence number of this cache line or the line + this instruction was extracted from. + </td> +</tr> + +<tr> +<td>InstId::fetchSeqNum</td> +<td>F</td> +<td>Fetch2</td> +<td>Fetch2 (as the inst. sequence number for branches)</td> +<td>Instruction fetch order assigned by Fetch2 when lines + are decomposed into instructions. + </td> +</tr> + +<tr> +<td>InstId::execSeqNum</td> +<td>E</td> +<td>Decode</td> +<td>Execute (to check instruction identity in queues/FUs/LSQ)</td> +<td>Instruction order after micro-op decomposition.</td> +</tr> + +</table> + +The sequence number fields are all independent of each other and although, for +instance, InstId::execSeqNum for an instruction will always be >= +InstId::fetchSeqNum, the comparison is not useful. + +The originating stage of each sequence number field keeps a counter for that +field which can be incremented in order to generate new, unique numbers. + +\subsection insts Instructions: MinorDynInst (dyn_inst.hh) + +MinorDynInst represents an instruction's progression through the pipeline. An +instruction can be three things: + +<table> +<tr> + <td><b>Thing</b></td> + <td><b>Predicate</b></td> + <td><b>Explanation</b></td> +</tr> +<tr> + <td>A bubble</td> + <td>MinorDynInst::isBubble()</td> + <td>no instruction at all, just a space-filler</td> +</tr> +<tr> + <td>A fault</td> + <td>MinorDynInst::isFault()</td> + <td>a fault to pass down the pipeline in an instruction's clothing</td> +</tr> +<tr> + <td>A decoded instruction</td> + <td>MinorDynInst::isInst()</td> + <td>instructions are actually passed to the gem5 decoder in Fetch2 and so + are created fully decoded. MinorDynInst::staticInst is the decoded + instruction form.</td> +</tr> +</table> + +Instructions are reference counted using the gem5 RefCountingPtr +(base/refcnt.hh) wrapper. They therefore usually appear as MinorDynInstPtr in +code. Note that as RefCountingPtr initialises as nullptr rather than an +object that supports BubbleIF::isBubble, passing raw MinorDynInstPtrs to +Queue%s and other similar structures from stage.hh without boxing is +dangerous. + +\subsection fld ForwardLineData (pipe_data.hh) + +ForwardLineData is used to pass cache lines from Fetch1 to Fetch2. Like +MinorDynInst%s, they can be bubbles (ForwardLineData::isBubble()), +fault-carrying or can contain a line (partial line) fetched by Fetch1. The +data carried by ForwardLineData is owned by a Packet object returned from +memory and is explicitly memory managed and do must be deleted once processed +(by Fetch2 deleting the Packet). + +\subsection fid ForwardInstData (pipe_data.hh) + +ForwardInstData can contain up to ForwardInstData::width() instructions in its +ForwardInstData::insts vector. This structure is used to carry instructions +between Fetch2, Decode and Execute and to store input buffer vectors in Decode +and Execute. + +\subsection fr Fetch1::FetchRequest (fetch1.hh) + +FetchRequests represent I-cache line fetch requests. The are used in the +memory queues of Fetch1 and are pushed into/popped from Packet::senderState +while traversing the memory system. + +FetchRequests contain a memory system Request (mem/request.hh) for that fetch +access, a packet (Packet, mem/packet.hh), if the request gets to memory, and a +fault field that can be populated with a TLB-sourced prefetch fault (if any). + +\subsection lsqr LSQ::LSQRequest (execute.hh) + +LSQRequests are similar to FetchRequests but for D-cache accesses. They carry +the instruction associated with a memory access. + +\section pipeline The pipeline + +\verbatim +------------------------------------------------------------------------------ + Key: + + [] : inter-stage BufferBuffer + ,--. + | | : pipeline stage + `--' + ---> : forward communication + <--- : backward communication + + rv : reservation information for input buffers + + ,------. ,------. ,------. ,-------. + (from --[]-v->|Fetch1|-[]->|Fetch2|-[]->|Decode|-[]->|Execute|--> (to Fetch1 + Execute) | | |<-[]-| |<-rv-| |<-rv-| | & Fetch2) + | `------'<-rv-| | | | | | + `-------------->| | | | | | + `------' `------' `-------' +------------------------------------------------------------------------------ +\endverbatim + +The four pipeline stages are connected together by MinorBuffer FIFO +(stage.hh, derived ultimately from TimeBuffer) structures which allow +inter-stage delays to be modelled. There is a MinorBuffer%s between adjacent +stages in the forward direction (for example: passing lines from Fetch1 to +Fetch2) and, between Fetch2 and Fetch1, a buffer in the backwards direction +carrying branch predictions. + +Stages Fetch2, Decode and Execute have input buffers which, each cycle, can +accept input data from the previous stage and can hold that data if the stage +is not ready to process it. Input buffers store data in the same form as it +is received and so Decode and Execute's input buffers contain the output +instruction vector (ForwardInstData (pipe_data.hh)) from their previous stages +with the instructions and bubbles in the same positions as a single buffer +entry. + +Stage input buffers provide a Reservable (stage.hh) interface to their +previous stages, to allow slots to be reserved in their input buffers, and +communicate their input buffer occupancy backwards to allow the previous stage +to plan whether it should make an output in a given cycle. + +\subsection events Event handling: MinorActivityRecorder (activity.hh, +pipeline.hh) + +Minor is essentially a cycle-callable model with some ability to skip cycles +based on pipeline activity. External events are mostly received by callbacks +(e.g. Fetch1::IcachePort::recvTimingResp) and cause the pipeline to be woken +up to service advancing request queues. + +Ticked (sim/ticked.hh) is a base class bringing together an evaluate +member function and a provided SimObject. It provides a Ticked::start/stop +interface to start and pause clock events from being periodically issued. +Pipeline is a derived class of Ticked. + +During evaluate calls, stages can signal that they still have work to do in +the next cycle by calling either MinorCPU::activityRecorder->activity() (for +non-callable related activity) or MinorCPU::wakeupOnEvent(<stageId>) (for +stage callback-related 'wakeup' activity). + +Pipeline::evaluate contains calls to evaluate for each unit and a test for +pipeline idling which can turns off the clock tick if no unit has signalled +that it may become active next cycle. + +Within Pipeline (pipeline.hh), the stages are evaluated in reverse order (and +so will ::evaluate in reverse order) and their backwards data can be +read immediately after being written in each cycle allowing output decisions +to be 'perfect' (allowing synchronous stalling of the whole pipeline). Branch +predictions from Fetch2 to Fetch1 can also be transported in 0 cycles making +fetch1ToFetch2BackwardDelay the only configurable delay which can be set as +low as 0 cycles. + +The MinorCPU::activateContext and MinorCPU::suspendContext interface can be +called to start and pause threads (threads in the MT sense) and to start and +pause the pipeline. Executing instructions can call this interface +(indirectly through the ThreadContext) to idle the CPU/their threads. + +\subsection stages Each pipeline stage + +In general, the behaviour of a stage (each cycle) is: + +\verbatim + evaluate: + push input to inputBuffer + setup references to input/output data slots + + do 'every cycle' 'step' tasks + + if there is input and there is space in the next stage: + process and generate a new output + maybe re-activate the stage + + send backwards data + + if the stage generated output to the following FIFO: + signal pipe activity + + if the stage has more processable input and space in the next stage: + re-activate the stage for the next cycle + + commit the push to the inputBuffer if that data hasn't all been used +\endverbatim + +The Execute stage differs from this model as its forward output (branch) data +is unconditionally sent to Fetch1 and Fetch2. To allow this behaviour, Fetch1 +and Fetch2 must be unconditionally receptive to that data. + +\subsection fetch1 Fetch1 stage + +Fetch1 is responsible for fetching cache lines or partial cache lines from the +I-cache and passing them on to Fetch2 to be decomposed into instructions. It +can receive 'change of stream' indications from both Execute and Fetch2 to +signal that it should change its internal fetch address and tag newly fetched +lines with new stream or prediction sequence numbers. When both Execute and +Fetch2 signal changes of stream at the same time, Fetch1 takes Execute's +change. + +Every line issued by Fetch1 will bear a unique line sequence number which can +be used for debugging stream changes. + +When fetching from the I-cache, Fetch1 will ask for data from the current +fetch address (Fetch1::pc) up to the end of the 'data snap' size set in the +parameter fetch1LineSnapWidth. Subsequent autonomous line fetches will fetch +whole lines at a snap boundary and of size fetch1LineWidth. + +Fetch1 will only initiate a memory fetch if it can reserve space in Fetch2 +input buffer. That input buffer serves an the fetch queue/LFL for the system. + +Fetch1 contains two queues: requests and transfers to handle the stages of +translating the address of a line fetch (via the TLB) and accommodating the +request/response of fetches to/from memory. + +Fetch requests from Fetch1 are pushed into the requests queue as newly +allocated FetchRequest objects once they have been sent to the ITLB with a +call to itb->translateTiming. + +A response from the TLB moves the request from the requests queue to the +transfers queue. If there is more than one entry in each queue, it is +possible to get a TLB response for request which is not at the head of the +requests queue. In that case, the TLB response is marked up as a state change +to Translated in the request object, and advancing the request to transfers +(and the memory system) is left to calls to Fetch1::stepQueues which is called +in the cycle following any event is received. + +Fetch1::tryToSendToTransfers is responsible for moving requests between the +two queues and issuing requests to memory. Failed TLB lookups (prefetch +aborts) continue to occupy space in the queues until they are recovered at the +head of transfers. + +Responses from memory change the request object state to Complete and +Fetch1::evaluate can pick up response data, package it in the ForwardLineData +object, and forward it to Fetch2%'s input buffer. + +As space is always reserved in Fetch2::inputBuffer, setting the input buffer's +size to 1 results in non-prefetching behaviour. + +When a change of stream occurs, translated requests queue members and +completed transfers queue members can be unconditionally discarded to make way +for new transfers. + +\subsection fetch2 Fetch2 stage + +Fetch2 receives a line from Fetch1 into its input buffer. The data in the +head line in that buffer is iterated over and separated into individual +instructions which are packed into a vector of instructions which can be +passed to Decode. Packing instructions can be aborted early if a fault is +found in either the input line as a whole or a decomposed instruction. + +\subsubsection bp Branch prediction + +Fetch2 contains the branch prediction mechanism. This is a wrapper around the +branch predictor interface provided by gem5 (cpu/pred/...). + +Branches are predicted for any control instructions found. If prediction is +attempted for an instruction, the MinorDynInst::triedToPredict flag is set on +that instruction. + +When a branch is predicted to take, the MinorDynInst::predictedTaken flag is +set and MinorDynInst::predictedTarget is set to the predicted target PC value. +The predicted branch instruction is then packed into Fetch2%'s output vector, +the prediction sequence number is incremented, and the branch is communicated +to Fetch1. + +After signalling a prediction, Fetch2 will discard its input buffer contents +and will reject any new lines which have the same stream sequence number as +that branch but have a different prediction sequence number. This allows +following sequentially fetched lines to be rejected without ignoring new lines +generated by a change of stream indicated from a 'real' branch from Execute +(which will have a new stream sequence number). + +The program counter value provided to Fetch2 by Fetch1 packets is only updated +when there is a change of stream. Fetch2::havePC indicates whether the PC +will be picked up from the next processed input line. Fetch2::havePC is +necessary to allow line-wrapping instructions to be tracked through decode. + +Branches (and instructions predicted to branch) which are processed by Execute +will generate BranchData (pipe_data.hh) data explaining the outcome of the +branch which is sent forwards to Fetch1 and Fetch2. Fetch1 uses this data to +change stream (and update its stream sequence number and address for new +lines). Fetch2 uses it to update the branch predictor. Minor does not +communicate branch data to the branch predictor for instructions which are +discarded on the way to commit. + +BranchData::BranchReason (pipe_data.hh) encodes the possible branch scenarios: + +<table> +<tr> + <td>Branch enum val.</td> + <td>In Execute</td> + <td>Fetch1 reaction</td> + <td>Fetch2 reaction</td> +</tr> +<tr> + <td>NoBranch</td> + <td>(output bubble data)</td> + <td>-</td> + <td>-</td> +</tr> +<tr> + <td>CorrectlyPredictedBranch</td> + <td>Predicted, taken</td> + <td>-</td> + <td>Update BP as taken branch</td> +</tr> +<tr> + <td>UnpredictedBranch</td> + <td>Not predicted, taken and was taken</td> + <td>New stream</td> + <td>Update BP as taken branch</td> +</tr> +<tr> + <td>BadlyPredictedBranch</td> + <td>Predicted, not taken</td> + <td>New stream to restore to old inst. source</td> + <td>Update BP as not taken branch</td> +</tr> +<tr> + <td>BadlyPredictedBranchTarget</td> + <td>Predicted, taken, but to a different target than predicted one</td> + <td>New stream</td> + <td>Update BTB to new target</td> +</tr> +<tr> + <td>SuspendThread</td> + <td>Hint to suspend fetching</td> + <td>Suspend fetch for this thread (branch to next inst. as wakeup + fetch addr)</td> + <td>-</td> +</tr> +<tr> + <td>Interrupt</td> + <td>Interrupt detected</td> + <td>New stream</td> + <td>-</td> +</tr> +</table> + +The parameter decodeInputWidth sets the number of instructions which can be +packed into the output per cycle. If the parameter fetch2CycleInput is true, +Decode can try to take instructions from more than one entry in its input +buffer per cycle. + +\subsection decode Decode stage + +Decode takes a vector of instructions from Fetch2 (via its input buffer) and +decomposes those instructions into micro-ops (if necessary) and packs them +into its output instruction vector. + +The parameter executeInputWidth sets the number of instructions which can be +packed into the output per cycle. If the parameter decodeCycleInput is true, +Decode can try to take instructions from more than one entry in its input +buffer per cycle. + +\subsection execute Execute stage + +Execute provides all the instruction execution and memory access mechanisms. +An instructions passage through Execute can take multiple cycles with its +precise timing modelled by a functional unit pipeline FIFO. + +A vector of instructions (possibly including fault 'instructions') is provided +to Execute by Decode and can be queued in the Execute input buffer before +being issued. Setting the parameter executeCycleInput allows execute to +examine more than one input buffer entry (more than one instruction vector). +The number of instructions in the input vector can be set with +executeInputWidth and the depth of the input buffer can be set with parameter +executeInputBufferSize. + +\subsubsection fus Functional units + +The Execute stage contains pipelines for each functional unit comprising the +computational core of the CPU. Functional units are configured via the +executeFuncUnits parameter. Each functional unit has a number of instruction +classes it supports, a stated delay between instruction issues, and a delay +from instruction issue to (possible) commit and an optional timing annotation +capable of more complicated timing. + +Each active cycle, Execute::evaluate performs this action: + +\verbatim + Execute::evaluate: + push input to inputBuffer + setup references to input/output data slots and branch output slot + + step D-cache interface queues (similar to Fetch1) + + if interrupt posted: + take interrupt (signalling branch to Fetch1/Fetch2) + else + commit instructions + issue new instructions + + advance functional unit pipelines + + reactivate Execute if the unit is still active + + commit the push to the inputBuffer if that data hasn't all been used +\endverbatim + +\subsubsection fifos Functional unit FIFOs + +Functional units are implemented as SelfStallingPipelines (stage.hh). These +are TimeBuffer FIFOs with two distinct 'push' and 'pop' wires. They respond +to SelfStallingPipeline::advance in the same way as TimeBuffers <b>unless</b> +there is data at the far, 'pop', end of the FIFO. A 'stalled' flag is +provided for signalling stalling and to allow a stall to be cleared. The +intention is to provide a pipeline for each functional unit which will never +advance an instruction out of that pipeline until it has been processed and +the pipeline is explicitly unstalled. + +The actions 'issue', 'commit', and 'advance' act on the functional units. + +\subsubsection issue Issue + +Issuing instructions involves iterating over both the input buffer +instructions and the heads of the functional units to try and issue +instructions in order. The number of instructions which can be issued each +cycle is limited by the parameter executeIssueLimit, how executeCycleInput is +set, the availability of pipeline space and the policy used to choose a +pipeline in which the instruction can be issued. + +At present, the only issue policy is strict round-robin visiting of each +pipeline with the given instructions in sequence. For greater flexibility, +better (and more specific policies) will need to be possible. + +Memory operation instructions traverse their functional units to perform their +EA calculations. On 'commit', the ExecContext::initiateAcc execution phase is +performed and any memory access is issued (via. ExecContext::{read,write}Mem +calling LSQ::pushRequest) to the LSQ. + +Note that faults are issued as if they are instructions and can (currently) be +issued to *any* functional unit. + +Every issued instruction is also pushed into the Execute::inFlightInsts queue. +Memory ref. instructions are pushing into Execute::inFUMemInsts queue. + +\subsubsection commit Commit + +Instructions are committed by examining the head of the Execute::inFlightInsts +queue (which is decorated with the functional unit number to which the +instruction was issued). Instructions which can then be found in their +functional units are executed and popped from Execute::inFlightInsts. + +Memory operation instructions are committed into the memory queues (as +described above) and exit their functional unit pipeline but are not popped +from the Execute::inFlightInsts queue. The Execute::inFUMemInsts queue +provides ordering to memory operations as they pass through the functional +units (maintaining issue order). On entering the LSQ, instructions are popped +from Execute::inFUMemInsts. + +If the parameter executeAllowEarlyMemoryIssue is set, memory operations can be +sent from their FU to the LSQ before reaching the head of +Execute::inFlightInsts but after their dependencies are met. +MinorDynInst::instToWaitFor is marked up with the latest dependent instruction +execSeqNum required to be committed for a memory operation to progress to the +LSQ. + +Once a memory response is available (by testing the head of +Execute::inFlightInsts against LSQ::findResponse), commit will process that +response (ExecContext::completeAcc) and pop the instruction from +Execute::inFlightInsts. + +Any branch, fault or interrupt will cause a stream sequence number change and +signal a branch to Fetch1/Fetch2. Only instructions with the current stream +sequence number will be issued and/or committed. + +\subsubsection advance Advance + +All non-stalled pipeline are advanced and may, thereafter, become stalled. +Potential activity in the next cycle is signalled if there are any +instructions remaining in any pipeline. + +\subsubsection sb Scoreboard + +The scoreboard (Scoreboard) is used to control instruction issue. It contains +a count of the number of in flight instructions which will write each general +purpose CPU integer or float register. Instructions will only be issued where +the scoreboard contains a count of 0 instructions which will write to one of +the instructions source registers. + +Once an instruction is issued, the scoreboard counts for each destination +register for an instruction will be incremented. + +The estimated delivery time of the instruction's result is marked up in the +scoreboard by adding the length of the issued-to FU to the current time. The +timings parameter on each FU provides a list of additional rules for +calculating the delivery time. These are documented in the parameter comments +in MinorCPU.py. + +On commit, (for memory operations, memory response commit) the scoreboard +counters for an instruction's source registers are decremented. will be +decremented. + +\subsubsection ifi Execute::inFlightInsts + +The Execute::inFlightInsts queue will always contain all instructions in +flight in Execute in the correct issue order. Execute::issue is the only +process which will push an instruction into the queue. Execute::commit is the +only process that can pop an instruction. + +\subsubsection lsq LSQ + +The LSQ can support multiple outstanding transactions to memory in a number of +conservative cases. + +There are three queues to contain requests: requests, transfers and the store +buffer. The requests and transfers queue operate in a similar manner to the +queues in Fetch1. The store buffer is used to decouple the delay of +completing store operations from following loads. + +Requests are issued to the DTLB as their instructions leave their functional +unit. At the head of requests, cacheable load requests can be sent to memory +and on to the transfers queue. Cacheable stores will be passed to transfers +unprocessed and progress that queue maintaining order with other transactions. + +The conditions in LSQ::tryToSendToTransfers dictate when requests can +be sent to memory. + +All uncacheable transactions, split transactions and locked transactions are +processed in order at the head of requests. Additionally, store results +residing in the store buffer can have their data forwarded to cacheable loads +(removing the need to perform a read from memory) but no cacheable load can be +issue to the transfers queue until that queue's stores have drained into the +store buffer. + +At the end of transfers, requests which are LSQ::LSQRequest::Complete (are +faulting, are cacheable stores, or have been sent to memory and received a +response) can be picked off by Execute and either committed +(ExecContext::completeAcc) and, for stores, be sent to the store buffer. + +Barrier instructions do not prevent cacheable loads from progressing to memory +but do cause a stream change which will discard that load. Stores will not be +committed to the store buffer if they are in the shadow of the barrier but +before the new instruction stream has arrived at Execute. As all other memory +transactions are delayed at the end of the requests queue until they are at +the head of Execute::inFlightInsts, they will be discarded by any barrier +stream change. + +After commit, LSQ::BarrierDataRequest requests are inserted into the +store buffer to track each barrier until all preceding memory transactions +have drained from the store buffer. No further memory transactions will be +issued from the ends of FUs until after the barrier has drained. + +\subsubsection drain Draining + +Draining is mostly handled by the Execute stage. When initiated by calling +MinorCPU::drain, Pipeline::evaluate checks the draining status of each unit +each cycle and keeps the pipeline active until draining is complete. It is +Pipeline that signals the completion of draining. Execute is triggered by +MinorCPU::drain and starts stepping through its Execute::DrainState state +machine, starting from state Execute::NotDraining, in this order: + +<table> +<tr> + <td><b>State</b></td> + <td><b>Meaning</b></td> +</tr> +<tr> + <td>Execute::NotDraining</td> + <td>Not trying to drain, normal execution</td> +</tr> +<tr> + <td>Execute::DrainCurrentInst</td> + <td>Draining micro-ops to complete inst.</td> +</tr> +<tr> + <td>Execute::DrainHaltFetch</td> + <td>Halt fetching instructions</td> +</tr> +<tr> + <td>Execute::DrainAllInsts</td> + <td>Discarding all instructions presented</td> +</tr> +</table> + +When complete, a drained Execute unit will be in the Execute::DrainAllInsts +state where it will continue to discard instructions but has no knowledge of +the drained state of the rest of the model. + +\section debug Debug options + +The model provides a number of debug flags which can be passed to gem5 with +the --debug-flags option. + +The available flags are: + +<table> +<tr> + <td><b>Debug flag</b></td> + <td><b>Unit which will generate debugging output</b></td> +</tr> +<tr> + <td>Activity</td> + <td>Debug ActivityMonitor actions</td> +</tr> +<tr> + <td>Branch</td> + <td>Fetch2 and Execute branch prediction decisions</td> +</tr> +<tr> + <td>MinorCPU</td> + <td>CPU global actions such as wakeup/thread suspension</td> +</tr> +<tr> + <td>Decode</td> + <td>Decode</td> +</tr> +<tr> + <td>MinorExec</td> + <td>Execute behaviour</td> +</tr> +<tr> + <td>Fetch</td> + <td>Fetch1 and Fetch2</td> +</tr> +<tr> + <td>MinorInterrupt</td> + <td>Execute interrupt handling</td> +</tr> +<tr> + <td>MinorMem</td> + <td>Execute memory interactions</td> +</tr> +<tr> + <td>MinorScoreboard</td> + <td>Execute scoreboard activity</td> +</tr> +<tr> + <td>MinorTrace</td> + <td>Generate MinorTrace cyclic state trace output (see below)</td> +</tr> +<tr> + <td>MinorTiming</td> + <td>MinorTiming instruction timing modification operations</td> +</tr> +</table> + +The group flag Minor enables all of the flags beginning with Minor. + +\section trace MinorTrace and minorview.py + +The debug flag MinorTrace causes cycle-by-cycle state data to be printed which +can then be processed and viewed by the minorview.py tool. This output is +very verbose and so it is recommended it only be used for small examples. + +\subsection traceformat MinorTrace format + +There are three types of line outputted by MinorTrace: + +\subsubsection state MinorTrace - Ticked unit cycle state + +For example: + +\verbatim + 110000: system.cpu.dcachePort: MinorTrace: state=MemoryRunning in_tlb_mem=0/0 +\endverbatim + +For each time step, the MinorTrace flag will cause one MinorTrace line to be +printed for every named element in the model. + +\subsubsection traceunit MinorInst - summaries of instructions issued by \ + Decode + +For example: + +\verbatim + 140000: system.cpu.execute: MinorInst: id=0/1.1/1/1.1 addr=0x5c \ + inst=" mov r0, #0" class=IntAlu +\endverbatim + +MinorInst lines are currently only generated for instructions which are +committed. + +\subsubsection tracefetch1 MinorLine - summaries of line fetches issued by \ + Fetch1 + +For example: + +\verbatim + 92000: system.cpu.icachePort: MinorLine: id=0/1.1/1 size=36 \ + vaddr=0x5c paddr=0x5c +\endverbatim + +\subsection minorview minorview.py + +Minorview (util/minorview.py) can be used to visualise the data created by +MinorTrace. + +\verbatim +usage: minorview.py [-h] [--picture picture-file] [--prefix name] + [--start-time time] [--end-time time] [--mini-views] + event-file + +Minor visualiser + +positional arguments: + event-file + +optional arguments: + -h, --help show this help message and exit + --picture picture-file + markup file containing blob information (default: + <minorview-path>/minor.pic) + --prefix name name prefix in trace for CPU to be visualised + (default: system.cpu) + --start-time time time of first event to load from file + --end-time time time of last event to load from file + --mini-views show tiny views of the next 10 time steps +\endverbatim + +Raw debugging output can be passed to minorview.py as the event-file. It will +pick out the MinorTrace lines and use other lines where units in the +simulation are named (such as system.cpu.dcachePort in the above example) will +appear as 'comments' when units are clicked on the visualiser. + +Clicking on a unit which contains instructions or lines will bring up a speech +bubble giving extra information derived from the MinorInst/MinorLine lines. + +--start-time and --end-time allow only sections of debug files to be loaded. + +--prefix allows the name prefix of the CPU to be inspected to be supplied. +This defaults to 'system.cpu'. + +In the visualiser, The buttons Start, End, Back, Forward, Play and Stop can be +used to control the displayed simulation time. + +The diagonally striped coloured blocks are showing the InstId of the +instruction or line they represent. Note that lines in Fetch1 and f1ToF2.F +only show the id fields of a line and that instructions in Fetch2, f2ToD, and +decode.inputBuffer do not yet have execute sequence numbers. The T/S.P/L/F.E +buttons can be used to toggle parts of InstId on and off to make it easier to +understand the display. Useful combinations are: + +<table> +<tr> + <td><b>Combination</b></td> + <td><b>Reason</b></td> +</tr> +<tr> + <td>E</td> + <td>just show the final execute sequence number</td> +</tr> +<tr> + <td>F/E</td> + <td>show the instruction-related numbers</td> +</tr> +<tr> + <td>S/P</td> + <td>show just the stream-related numbers (watch the stream sequence + change with branches and not change with predicted branches)</td> +</tr> +<tr> + <td>S/E</td> + <td>show instructions and their stream</td> +</tr> +</table> + +The key to the right shows all the displayable colours (some of the colour +choices are quite bad!): + +<table> +<tr> + <td><b>Symbol</b></td> + <td><b>Meaning</b></td> +</tr> +<tr> + <td>U</td> + <td>Unknown data</td> +</tr> +<tr> + <td>B</td> + <td>Blocked stage</td> +</tr> +<tr> + <td>-</td> + <td>Bubble</td> +</tr> +<tr> + <td>E</td> + <td>Empty queue slot</td> +</tr> +<tr> + <td>R</td> + <td>Reserved queue slot</td> +</tr> +<tr> + <td>F</td> + <td>Fault</td> +</tr> +<tr> + <td>r</td> + <td>Read (used as the leftmost stripe on data in the dcachePort)</td> +</tr> +<tr> + <td>w</td> + <td>Write " "</td> +</tr> +<tr> + <td>0 to 9</td> + <td>last decimal digit of the corresponding data</td> +</tr> +</table> + +\verbatim + + ,---------------. .--------------. *U + | |=|->|=|->|=| | ||=|||->||->|| | *- <- Fetch queues/LSQ + `---------------' `--------------' *R + === ====== *w <- Activity/Stage activity + ,--------------. *1 + ,--. ,. ,. | ============ | *3 <- Scoreboard + | |-\[]-\||-\[]-\||-\[]-\| ============ | *5 <- Execute::inFlightInsts + | | :[] :||-/[]-/||-/[]-/| -. -------- | *7 + | |-/[]-/|| ^ || | | --------- | *9 + | | || | || | | ------ | +[]->| | ->|| | || | | ---- | + | |<-[]<-||<-+-<-||<-[]<-| | ------ |->[] <- Execute to Fetch1, + '--` `' ^ `' | -' ------ | Fetch2 branch data + ---. | ---. `--------------' + ---' | ---' ^ ^ + | ^ | `------------ Execute + MinorBuffer ----' input `-------------------- Execute input buffer + buffer +\endverbatim + +Stages show the colours of the instructions currently being +generated/processed. + +Forward FIFOs between stages show the data being pushed into them at the +current tick (to the left), the data in transit, and the data available at +their outputs (to the right). + +The backwards FIFO between Fetch2 and Fetch1 shows branch prediction data. + +In general, all displayed data is correct at the end of a cycle's activity at +the time indicated but before the inter-stage FIFOs are ticked. Each FIFO +has, therefore an extra slot to show the asserted new input data, and all the +data currently within the FIFO. + +Input buffers for each stage are shown below the corresponding stage and show +the contents of those buffers as horizontal strips. Strips marked as reserved +(cyan by default) are reserved to be filled by the previous stage. An input +buffer with all reserved or occupied slots will, therefore, block the previous +stage from generating output. + +Fetch queues and LSQ show the lines/instructions in the queues of each +interface and show the number of lines/instructions in TLB and memory in the +two striped colours of the top of their frames. + +Inside Execute, the horizontal bars represent the individual FU pipelines. +The vertical bar to the left is the input buffer and the bar to the right, the +instructions committed this cycle. The background of Execute shows +instructions which are being committed this cycle in their original FU +pipeline positions. + +The strip at the top of the Execute block shows the current streamSeqNum that +Execute is committing. A similar stripe at the top of Fetch1 shows that +stage's expected streamSeqNum and the stripe at the top of Fetch2 shows its +issuing predictionSeqNum. + +The scoreboard shows the number of instructions in flight which will commit a +result to the register in the position shown. The scoreboard contains slots +for each integer and floating point register. + +The Execute::inFlightInsts queue shows all the instructions in flight in +Execute with the oldest instruction (the next instruction to be committed) to +the right. + +'Stage activity' shows the signalled activity (as E/1) for each stage (with +CPU miscellaneous activity to the left) + +'Activity' show a count of stage and pipe activity. + +\subsection picformat minor.pic format + +The minor.pic file (src/minor/minor.pic) describes the layout of the +models blocks on the visualiser. Its format is described in the supplied +minor.pic file. + +*/ + +} diff --git a/src/sim/SConscript b/src/sim/SConscript index 5a5c1ab8a..9f9022f30 100644 --- a/src/sim/SConscript +++ b/src/sim/SConscript @@ -32,6 +32,7 @@ Import('*') SimObject('BaseTLB.py') SimObject('ClockedObject.py') +SimObject('TickedObject.py') SimObject('Root.py') SimObject('ClockDomain.py') SimObject('VoltageDomain.py') @@ -51,6 +52,7 @@ Source('serialize.cc') Source('drain.cc') Source('sim_events.cc') Source('sim_object.cc') +Source('ticked_object.cc') Source('simulate.cc') Source('stat_control.cc') Source('clock_domain.cc') diff --git a/src/sim/TickedObject.py b/src/sim/TickedObject.py new file mode 100644 index 000000000..a566aac92 --- /dev/null +++ b/src/sim/TickedObject.py @@ -0,0 +1,43 @@ +# Copyright (c) 2014 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: Andrew Bardsley + +from ClockedObject import ClockedObject + +class TickedObject(ClockedObject): + type = 'TickedObject' + abstract = True + cxx_header = "sim/ticked_object.hh" diff --git a/src/sim/ticked_object.cc b/src/sim/ticked_object.cc new file mode 100644 index 000000000..22a149388 --- /dev/null +++ b/src/sim/ticked_object.cc @@ -0,0 +1,116 @@ +/* + * Copyright (c) 2013-2014 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: Andrew Bardsley + */ + +#include "sim/ticked_object.hh" + +Ticked::Ticked(ClockedObject &object_, + Stats::Scalar *imported_num_cycles, + Event::Priority priority) : + object(object_), + event(*this, priority), + running(false), + lastStopped(0), + /* Allocate numCycles if an external stat wasn't passed in */ + numCyclesLocal((imported_num_cycles ? NULL : new Stats::Scalar)), + numCycles((imported_num_cycles ? *imported_num_cycles : + *numCyclesLocal)) +{ } + +void +Ticked::regStats() +{ + if (numCyclesLocal) { + numCycles + .name(object.name() + ".tickCycles") + .desc("Number of cycles that the object ticked or was stopped"); + } + + tickCycles + .name(object.name() + ".tickCycles") + .desc("Number of cycles that the object actually ticked"); + + idleCycles + .name(object.name() + ".idleCycles") + .desc("Total number of cycles that the object has spent stopped"); + idleCycles = numCycles - tickCycles; +} + +void +Ticked::serialize(std::ostream &os) +{ + uint64_t lastStoppedUint = lastStopped; + + paramOut(os, "lastStopped", lastStoppedUint); +} + +void +Ticked::unserialize(Checkpoint *cp, const std::string §ion) +{ + uint64_t lastStoppedUint; + + paramIn(cp, section, "lastStopped", lastStoppedUint); + + lastStopped = Cycles(lastStoppedUint); +} + +TickedObject::TickedObject(TickedObjectParams *params, + Event::Priority priority) : + ClockedObject(params), + /* Make numCycles in Ticked */ + Ticked(*this, NULL, priority) +{ } + +void +TickedObject::regStats() +{ + Ticked::regStats(); +} + +void +TickedObject::serialize(std::ostream &os) +{ + Ticked::serialize(os); + ClockedObject::serialize(os); +} +void +TickedObject::unserialize(Checkpoint *cp, const std::string §ion) +{ + Ticked::unserialize(cp, section); + ClockedObject::unserialize(cp, section); +} diff --git a/src/sim/ticked_object.hh b/src/sim/ticked_object.hh new file mode 100644 index 000000000..5bca92443 --- /dev/null +++ b/src/sim/ticked_object.hh @@ -0,0 +1,191 @@ +/* + * Copyright (c) 2013-2014 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: Andrew Bardsley + */ + +/** + * @file + * + * Base classes for ClockedObjects which have evaluate functions to + * look like clock ticking operations. TickedObject attaches gem5's event + * queue to Ticked to apply actual scheduling. + */ + +#ifndef __SIM_TICKED_OBJECT_HH__ +#define __SIM_TICKED_OBJECT_HH__ + +#include "params/TickedObject.hh" +#include "sim/clocked_object.hh" + +/** Ticked attaches gem5's event queue/scheduler to evaluate + * calls and provides a start/stop interface to ticking. + * + * Ticked is not a ClockedObject but can be attached to one by + * inheritance and by calling regStats, serialize/unserialize */ +class Ticked +{ + protected: + /** An event to call process periodically */ + class ClockEvent : public Event + { + public: + Ticked &owner; + + ClockEvent(Ticked &owner_, Priority priority) : + Event(priority), + owner(owner_) + { } + + /** Evaluate and reschedule */ + void + process() + { + ++owner.tickCycles; + ++owner.numCycles; + owner.evaluate(); + if (owner.running) { + owner.object.schedule(this, + owner.object.clockEdge(Cycles(1))); + } + } + }; + + friend class ClockEvent; + + /** ClockedObject who is responsible for this Ticked's actions/stats */ + ClockedObject &object; + + /** The single instance of ClockEvent used */ + ClockEvent event; + + /** Have I been started? and am not stopped */ + bool running; + + /** Time of last stop event to calculate run time */ + Cycles lastStopped; + + private: + /** Locally allocated stats */ + Stats::Scalar *numCyclesLocal; + + protected: + /** Total number of cycles either ticked or spend stopped */ + Stats::Scalar &numCycles; + + /** Number of cycles ticked */ + Stats::Scalar tickCycles; + + /** Number of cycles stopped */ + Stats::Formula idleCycles; + + public: + Ticked(ClockedObject &object_, + Stats::Scalar *imported_num_cycles = NULL, + Event::Priority priority = Event::CPU_Tick_Pri); + + virtual ~Ticked() { } + + /** Register {num,ticks}Cycles if necessary. If numCycles is + * imported, be sure to register it *before* calling this regStats */ + void regStats(); + + /** Start ticking */ + void + start() + { + if (!running) { + if (!event.scheduled()) + object.schedule(event, object.clockEdge(Cycles(1))); + running = true; + numCycles += cyclesSinceLastStopped(); + } + } + + /** How long have we been stopped for? */ + Cycles + cyclesSinceLastStopped() const + { + return object.curCycle() - lastStopped; + } + + /** Reset stopped time to current time */ + void + resetLastStopped() + { + lastStopped = object.curCycle(); + } + + /** Cancel the next tick event and issue no more */ + void + stop() + { + if (running) { + if (event.scheduled()) + object.deschedule(event); + running = false; + resetLastStopped(); + } + } + + /** Checkpoint lastStopped */ + void serialize(std::ostream &os); + void unserialize(Checkpoint *cp, const std::string §ion); + + /** Action to call on the clock tick */ + virtual void evaluate() = 0; +}; + +/** TickedObject attaches Ticked to ClockedObject and can be used as + * a base class where ticked operation */ +class TickedObject : public ClockedObject, public Ticked +{ + public: + TickedObject(TickedObjectParams *params, + Event::Priority priority = Event::CPU_Tick_Pri); + + /** Disambiguate to make these functions overload correctly */ + using ClockedObject::regStats; + using ClockedObject::serialize; + using ClockedObject::unserialize; + + /** Pass on regStats, serialize etc. onto Ticked */ + void regStats(); + void serialize(std::ostream &os); + void unserialize(Checkpoint *cp, const std::string §ion); +}; + +#endif /* __SIM_TICKED_OBJECT_HH__ */ diff --git a/util/minorview.py b/util/minorview.py new file mode 100755 index 000000000..1a6b47b01 --- /dev/null +++ b/util/minorview.py @@ -0,0 +1,107 @@ +#!/usr/bin/env python +# +# Copyright (c) 2013 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: Andrew Bardsley +# +# minorview.py: Minorview visuliser for MinorCPU model MinorTrace output +# + +import gtk +import os +import sys +import argparse + +# Find MinorView modules even if not called from minorview directory +minorviewDir = os.path.dirname(os.path.realpath(__file__)) +sys.path.append(minorviewDir) + +from minorview.model import BlobModel +from minorview.view import BlobView, BlobController, BlobWindow +from minorview.point import Point + +if __name__ == '__main__': + parser = argparse.ArgumentParser(description='Minor visualiser') + + parser.add_argument('--picture', metavar='picture-file', + default=minorviewDir + '/minorview/minor.pic', + help='markup file containing blob information ' + + '(default: <minorview-path>/minor.pic)') + parser.add_argument('--prefix', metavar='name', default='system.cpu', + help='name prefix in trace for CPU to be visualised (default: ' + + 'system.cpu)') + parser.add_argument('--start-time', metavar='time', type=int, default=0, + help='time of first event to load from file') + parser.add_argument('--end-time', metavar='time', type=int, default=None, + help='time of last event to load from file') + parser.add_argument('--mini-views', action='store_true', default=False, + help='show tiny views of the next 10 time steps') + parser.add_argument('eventFile', metavar='event-file', default='ev') + + args = parser.parse_args(sys.argv[1:]) + + model = BlobModel(unitNamePrefix=args.prefix) + + if args.picture and os.access(args.picture, os.O_RDONLY): + model.load_picture(args.picture) + else: + parser.error('Can\'t read picture file: ' + args.picture) + + # Make the key objects + view = BlobView(model) + controller = BlobController(model, view, + defaultEventFile=args.eventFile, + defaultPictureFile=args.picture) + window = BlobWindow(model, view, controller) + window.add_control_bar(controller.bar) + + # Miniviews allow future timesteps to appear at the bottom of the + # display. + if args.mini_views: + window.miniViewCount = 10 + + window.show_window() + + if args.eventFile and os.access(args.eventFile, os.O_RDONLY): + controller.startTime = args.start_time + controller.endTime = args.end_time + model.load_events(args.eventFile, startTime=args.start_time, + endTime=args.end_time) + controller.set_time_index(0) + else: + parser.error('Can\'t read event file: ' + args.eventFile) + + gtk.main() diff --git a/util/minorview/__init__.py b/util/minorview/__init__.py new file mode 100644 index 000000000..0b957dcec --- /dev/null +++ b/util/minorview/__init__.py @@ -0,0 +1,36 @@ +# Copyright (c) 2013 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: Andrew Bardsley diff --git a/util/minorview/blobs.py b/util/minorview/blobs.py new file mode 100644 index 000000000..32d08dfa2 --- /dev/null +++ b/util/minorview/blobs.py @@ -0,0 +1,461 @@ +# Copyright (c) 2013 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: Andrew Bardsley +# +# blobs.py: Blobs are the visual blocks, arrows and other coloured +# objects on the visualiser. This file contains Blob definition and +# their rendering instructions in pygtk/cairo. +# + +import pygtk +pygtk.require('2.0') +import gtk +import gobject +import cairo +import re +import math + +from point import Point +import parse +import colours +from colours import backgroundColour, black +import model + +def centre_size_to_sides(centre, size): + """Returns a 4-tuple of the relevant ordinates of the left, + right, top and bottom sides of the described rectangle""" + (x, y) = centre.to_pair() + (half_width, half_height) = (size.scale(0.5)).to_pair() + left = x - half_width + right = x + half_width + top = y - half_height + bottom = y + half_height + return (left, right, top, bottom) + +def box(cr, centre, size): + """Draw a simple box""" + (left, right, top, bottom) = centre_size_to_sides(centre, size) + cr.move_to(left, top) + cr.line_to(right, top) + cr.line_to(right, bottom) + cr.line_to(left, bottom) + cr.close_path() + +def stroke_and_fill(cr, colour): + """Stroke with the current colour then fill the same path with the + given colour""" + join = cr.get_line_join() + cr.set_line_join(gtk.gdk.JOIN_ROUND) + cr.close_path() + cr.set_source_color(backgroundColour) + cr.stroke_preserve() + cr.set_source_color(colour) + cr.fill() + cr.set_line_join(join) + +def striped_box(cr, centre, size, colours): + """Fill a rectangle (without outline) striped with the colours given""" + num_colours = len(colours) + if num_colours == 0: + box(cr, centre, size) + cr.set_source_color(backgroundColour) + cr.fill() + elif num_colours == 1: + box(cr, centre, size) + stroke_and_fill(cr, colours[0]) + else: + (left, right, top, bottom) = centre_size_to_sides(centre, size) + (width, height) = size.to_pair() + x_stripe_width = width / num_colours + half_x_stripe_width = x_stripe_width / 2.0 + # Left triangle + cr.move_to(left, bottom) + cr.line_to(left + half_x_stripe_width, bottom) + cr.line_to(left + x_stripe_width + half_x_stripe_width, top) + cr.line_to(left, top) + stroke_and_fill(cr, colours[0]) + # Stripes + for i in xrange(1, num_colours - 1): + xOffset = x_stripe_width * i + cr.move_to(left + xOffset - half_x_stripe_width, bottom) + cr.line_to(left + xOffset + half_x_stripe_width, bottom) + cr.line_to(left + xOffset + x_stripe_width + + half_x_stripe_width, top) + cr.line_to(left + xOffset + x_stripe_width - + half_x_stripe_width, top) + stroke_and_fill(cr, colours[i]) + # Right triangle + cr.move_to((right - x_stripe_width) - half_x_stripe_width, bottom) + cr.line_to(right, bottom) + cr.line_to(right, top) + cr.line_to((right - x_stripe_width) + half_x_stripe_width, top) + stroke_and_fill(cr, colours[num_colours - 1]) + +def speech_bubble(cr, top_left, size, unit): + """Draw a speech bubble with 'size'-sized internal space with its + top left corner at Point(2.0 * unit, 2.0 * unit)""" + def local_arc(centre, angleFrom, angleTo): + cr.arc(centre.x, centre.y, unit, angleFrom * math.pi, + angleTo * math.pi) + + cr.move_to(*top_left.to_pair()) + cr.rel_line_to(unit * 2.0, unit) + cr.rel_line_to(size.x, 0.0) + local_arc(top_left + Point(size.x + unit * 2.0, unit * 2.0), -0.5, 0.0) + cr.rel_line_to(0.0, size.y) + local_arc(top_left + Point(size.x + unit * 2.0, size.y + unit * 2.0), + 0, 0.5) + cr.rel_line_to(-size.x, 0.0) + local_arc(top_left + Point(unit * 2.0, size.y + unit * 2.0), 0.5, 1.0) + cr.rel_line_to(0, -size.y) + cr.close_path() + +def open_bottom(cr, centre, size): + """Draw a box with left, top and right sides""" + (left, right, top, bottom) = centre_size_to_sides(centre, size) + cr.move_to(left, bottom) + cr.line_to(left, top) + cr.line_to(right, top) + cr.line_to(right, bottom) + +def fifo(cr, centre, size): + """Draw just the vertical sides of a box""" + (left, right, top, bottom) = centre_size_to_sides(centre, size) + cr.move_to(left, bottom) + cr.line_to(left, top) + cr.move_to(right, bottom) + cr.line_to(right, top) + +def cross(cr, centre, size): + """Draw a cross parallel with the axes""" + (left, right, top, bottom) = centre_size_to_sides(centre, size) + (x, y) = centre.to_pair() + cr.move_to(left, y) + cr.line_to(right, y) + cr.move_to(x, top) + cr.line_to(x, bottom) + +class Blob(object): + """Blob super class""" + def __init__(self, picChar, unit, topLeft, colour, size = Point(1,1)): + self.picChar = picChar + self.unit = unit + self.displayName = unit + self.nameLoc = 'top' + self.topLeft = topLeft + self.colour = colour + self.size = size + self.border = 1.0 + self.dataSelect = model.BlobDataSelect() + self.shorten = 0 + + def render(self, cr, view, event, select, time): + """Render this blob with the given event's data. Returns either + None or a pair of (centre, size) in device coordinates for the drawn + blob. The return value can be used to detect if mouse clicks on + the canvas are within the blob""" + return None + +class Block(Blob): + """Blocks are rectangular blogs colourable with a 2D grid of striped + blocks. visualDecoder specifies how event data becomes this coloured + grid""" + def __init__(self, picChar, unit, topLeft=Point(0,0), + colour=colours.black, + size=Point(1,1)): + super(Block,self).__init__(picChar, unit, topLeft, colour, + size = size) + # {horiz, vert} + self.stripDir = 'horiz' + # {LR, RL}: LR means the first strip will be on the left/top, + # RL means the first strip will be on the right/bottom + self.stripOrd = 'LR' + # Number of blank strips if this is a frame + self.blankStrips = 0 + # {box, fifo, openBottom} + self.shape = 'box' + self.visualDecoder = None + + def render(self, cr, view, event, select, time): + # Find the right event, visuals and sizes for things + if event is None or self.displayName.startswith('_'): + event = model.BlobEvent(self.unit, time) + + if self.picChar in event.visuals: + strips = event.visuals[self.picChar].to_striped_block( + select & self.dataSelect) + else: + strips = [[[colours.unknownColour]]] + + if self.stripOrd == 'RL': + strips.reverse() + + if len(strips) == 0: + strips = [[colours.errorColour]] + print 'Problem with the colour of event:', event + + num_strips = len(strips) + strip_proportion = 1.0 / num_strips + first_strip_offset = (num_strips / 2.0) - 0.5 + + # Adjust blocks with 'shorten' attribute to the length of the data + size = Point(*self.size.to_pair()) + if self.shorten != 0 and self.size.x > (num_strips * self.shorten): + size.x = num_strips * self.shorten + + box_size = size - view.blobIndentFactor.scale(2) + + # Now do cr sensitive things + cr.save() + cr.scale(*view.pitch.to_pair()) + cr.translate(*self.topLeft.to_pair()) + cr.translate(*(size - Point(1,1)).scale(0.5).to_pair()) + + translated_centre = Point(*cr.user_to_device(0.0, 0.0)) + translated_size = \ + Point(*cr.user_to_device_distance(*size.to_pair())) + + # The 2D grid is a grid of strips of blocks. Data [[1,2],[3]] + # is 2 strips of 2 and 1 blocks respectively. + # if stripDir == 'horiz', strips are stacked vertically + # from top to bottom if stripOrd == 'LR' or bottom to top if + # stripOrd == 'RL'. + # if stripDir == 'vert', strips are stacked horizontally + # from left to right if stripOf == 'LR' or right to left if + # stripOrd == 'RL'. + + strip_is_horiz = self.stripDir == 'horiz' + + if strip_is_horiz: + strip_step_base = Point(1.0,0.0) + block_step_base = Point(0.0,1.0) + else: + strip_step_base = Point(0.0,1.0) + block_step_base = Point(1.0,0.0) + + strip_size = (box_size * (strip_step_base.scale(strip_proportion) + + block_step_base)) + strip_step = strip_size * strip_step_base + strip_centre = Point(0,0) - (strip_size * + strip_step_base.scale(first_strip_offset)) + + cr.set_line_width(view.midLineWidth / view.pitch.x) + + # Draw the strips and their blocks + for strip_index in xrange(0, num_strips): + num_blocks = len(strips[strip_index]) + block_proportion = 1.0 / num_blocks + firstBlockOffset = (num_blocks / 2.0) - 0.5 + + block_size = (strip_size * + (block_step_base.scale(block_proportion) + + strip_step_base)) + block_step = block_size * block_step_base + block_centre = (strip_centre + strip_step.scale(strip_index) - + (block_size * block_step_base.scale(firstBlockOffset))) + + for block_index in xrange(0, num_blocks): + striped_box(cr, block_centre + + block_step.scale(block_index), block_size, + strips[strip_index][block_index]) + + cr.set_font_size(0.7) + if self.border > 0.5: + weight = cairo.FONT_WEIGHT_BOLD + else: + weight = cairo.FONT_WEIGHT_NORMAL + cr.select_font_face('Helvetica', cairo.FONT_SLANT_NORMAL, + weight) + + xb, yb, width, height, dx, dy = cr.text_extents(self.displayName) + + text_comfort_space = 0.15 + + if self.nameLoc == 'left': + # Position text vertically along left side, top aligned + cr.save() + cr.rotate(- (math.pi / 2.0)) + text_point = Point(size.y, size.x).scale(0.5) * Point(-1, -1) + text_point += Point(max(0, size.y - width), 0) + text_point += Point(-text_comfort_space, -text_comfort_space) + else: # Including top + # Position text above the top left hand corner + text_point = size.scale(0.5) * Point(-1,-1) + text_point += Point(0.00, -text_comfort_space) + + if (self.displayName != '' and + not self.displayName.startswith('_')): + cr.set_source_color(self.colour) + cr.move_to(*text_point.to_pair()) + cr.show_text(self.displayName) + + if self.nameLoc == 'left': + cr.restore() + + # Draw the outline shape + cr.save() + if strip_is_horiz: + cr.rotate(- (math.pi / 2.0)) + box_size = Point(box_size.y, box_size.x) + + if self.stripOrd == "RL": + cr.rotate(math.pi) + + if self.shape == 'box': + box(cr, Point(0,0), box_size) + elif self.shape == 'openBottom': + open_bottom(cr, Point(0,0), box_size) + elif self.shape == 'fifo': + fifo(cr, Point(0,0), box_size) + cr.restore() + + # Restore scale and stroke the outline + cr.restore() + cr.set_source_color(self.colour) + cr.set_line_width(view.thickLineWidth * self.border) + cr.stroke() + + # Return blob size/position + if self.unit == '_': + return None + else: + return (translated_centre, translated_size) + +class Key(Blob): + """Draw a key to the special (and numeric colours) with swatches of the + colours half as wide as the key""" + def __init__(self, picChar, unit, topLeft, colour=colours.black, + size=Point(1,1)): + super(Key,self).__init__(picChar, unit, topLeft, colour, size = size) + self.colours = 'BBBB' + self.displayName = unit + + def render(self, cr, view, event, select, time): + cr.save() + cr.scale(*view.pitch.to_pair()) + cr.translate(*self.topLeft.to_pair()) + # cr.translate(*(self.size - Point(1,1)).scale(0.5).to_pair()) + half_width = self.size.x / 2.0 + cr.translate(*(self.size - Point(1.0 + half_width,1.0)).scale(0.5). + to_pair()) + + num_colours = len(self.colours) + cr.set_line_width(view.midLineWidth / view.pitch.x) + + blob_size = (Point(half_width,0.0) + + (self.size * Point(0.0,1.0 / num_colours))) + blob_step = Point(0.0,1.0) * blob_size + first_blob_centre = (Point(0.0,0.0) - + blob_step.scale((num_colours / 2.0) - 0.5)) + + cr.set_source_color(self.colour) + cr.set_line_width(view.thinLineWidth / view.pitch.x) + + blob_proportion = 0.8 + + real_blob_size = blob_size.scale(blob_proportion) + + cr.set_font_size(0.8 * blob_size.y * blob_proportion) + cr.select_font_face('Helvetica', cairo.FONT_SLANT_NORMAL, + cairo.FONT_WEIGHT_BOLD) + + for i in xrange(0, num_colours): + centre = first_blob_centre + blob_step.scale(i) + box(cr, centre, real_blob_size) + + colour_char = self.colours[i] + if colour_char.isdigit(): + cr.set_source_color(colours.number_to_colour( + int(colour_char))) + label = '...' + colour_char + else: + cr.set_source_color(model.special_state_colours[colour_char]) + label = model.special_state_names[colour_char] + + cr.fill_preserve() + cr.set_source_color(self.colour) + cr.stroke() + + xb, yb, width, height, dx, dy = cr.text_extents(label) + + text_left = (centre + (Point(0.5,0.0) * blob_size) + + Point(0.0, height / 2.0)) + + cr.move_to(*text_left.to_pair()) + cr.show_text(label) + +class Arrow(Blob): + """Draw a left or right facing arrow""" + def __init__(self, unit, topLeft, colour=colours.black, + size=Point(1.0,1.0), direc='right'): + super(Arrow,self).__init__(unit, unit, topLeft, colour, size = size) + self.direc = direc + + def render(self, cr, view, event, select, time): + cr.save() + cr.scale(*view.pitch.to_pair()) + cr.translate(*self.topLeft.to_pair()) + cr.translate(*(self.size - Point(1,1)).scale(0.5).to_pair()) + cr.scale(*self.size.to_pair()) + (blob_indent_x, blob_indent_y) = \ + (view.blobIndentFactor / self.size).to_pair() + left = -0.5 - blob_indent_x + right = 0.5 + blob_indent_x + + thickness = 0.2 + flare = 0.2 + + if self.direc == 'left': + cr.rotate(math.pi) + + cr.move_to(left, -thickness) + cr.line_to(0.0, -thickness) + cr.line_to(0.0, -(thickness + flare)) + cr.line_to(right, 0) + # Break arrow to prevent the point ruining the appearance of boxes + cr.move_to(right, 0) + cr.line_to(0.0, (thickness + flare)) + cr.line_to(0.0, +thickness) + cr.line_to(left, +thickness) + + cr.restore() + + # Draw arrow a bit more lightly than the standard line width + cr.set_line_width(cr.get_line_width() * 0.75) + cr.set_source_color(self.colour) + cr.stroke() + + return None diff --git a/util/minorview/colours.py b/util/minorview/colours.py new file mode 100644 index 000000000..b8394b019 --- /dev/null +++ b/util/minorview/colours.py @@ -0,0 +1,68 @@ +# Copyright (c) 2013 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: Andrew Bardsley + +import gtk + +# All the miscellaneous colours used in the interface +unknownColour = gtk.gdk.color_parse('magenta') +blockedColour = gtk.gdk.color_parse('grey') +bubbleColour = gtk.gdk.color_parse('bisque') +emptySlotColour = gtk.gdk.color_parse('grey90') +reservedSlotColour = gtk.gdk.color_parse('cyan') +errorColour = gtk.gdk.color_parse('blue') +backgroundColour = gtk.gdk.color_parse('white') +faultColour = gtk.gdk.color_parse('dark cyan') +readColour = gtk.gdk.color_parse('red') +writeColour = gtk.gdk.color_parse('white') + +black = gtk.gdk.color_parse('black') + +def name_to_colour(name): + """Convert a colour name to a GdkColor""" + try: + ret = gtk.gdk.color_parse(name) + except: + ret = unknownColour + return ret + +number_colour_code = map(name_to_colour, ['black', 'brown', 'red', 'orange', + 'yellow', 'green', 'blue', 'violet', 'grey', 'white']) + +def number_to_colour(num): + """Convert the last decimal digit of an integer into a resistor + stripe colour""" + return number_colour_code[num % 10] diff --git a/util/minorview/minor.pic b/util/minorview/minor.pic new file mode 100644 index 000000000..52731d66f --- /dev/null +++ b/util/minorview/minor.pic @@ -0,0 +1,154 @@ +# Copyright (c) 2013 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: Andrew Bardsley +# +# minor.pic: Pipeline appearance of the Minor pipeline for minorview + +# Markup of the pipeline blocks. +# '>' and '<' are connecting arrows. +# '/', ':' and '\' mark multi-line arrows. +# All other (non-space) character rectangles are the shapes of the +# corresponding blocks below the markup. + +<<< + IPIPIPIPIPIPIPIPIP LSLSLSLSLSLSLSLSLSLSLSLSLSLSLSLSLSLSLSLSLS KKKKKKKKKK + IP IP LS LS KK KK + IPiririr-\itititIP LSimimim drdrdr-\dtdtdtdt sbsbsbsbsbsbLS KK KK + IPiririr-/itititIP LSimimim drdrdr-/dtdtdtdt sbsbsbsbsbsbLS KK KK + IPIPIPIPIPIPIPIPIP LSLSLSLSLSLSLSLSLSLSLSLSLSLSLSLSLSLSLSLSLS KK KK + KK KK + KK KK + acacac sasasasasasasasa EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE KK KK + acacac sasasasasasasasa EE EE KK KK + EEscscscscscscscscscscscscscscscscscscscEE KK KK + EEscscscscscscscscscscscscscscscscscscscEE KK KK + EE EE KK KK + F1F1F1F1-\1212-\F2F2F2F2-\2D2D-\DDDD-\DEDE-\EEiqiqiqiqiqiqiqiqiqiqiqiqiqiqiqiqiqiqiqEE KK KK + F1 F1 :1212 :F2 F2 :2D2D :DDDD :DEDE :EEiqiqiqiqiqiqiqiqiqiqiqiqiqiqiqiqiqiqiqEE KK KK + F1fefeF1 :1212 :F2fifiF2 :2D2D :DDDD :DEDE :EE EE KK KK + F1fefeF1 :1212 :F2fifiF2 :2D2D :DDDD :DEDE :EEEiEiEiEi f0f0f0f0f0f0f0f0f0f0f0 ccccEE KK KK + F1fefeF1 :1212 :F2fifiF2 :2D2D :DDDD :DEDE :EEEiEiEiEi f0f0f0f0f0f0f0f0f0f0f0 ccccEE KK KK + F1fefeF1 :1212 :F2fifiF2-/2D2D-/DDDD-/DEDE-/EEEiEiEiEi f1f1f1f1f1f1f1f1f1f1f1 ccccEE KKKKKKKKKK + F1fefeF1 :1212 :F2fifiF2 DDDD EEEiEiEiEi f1f1f1f1f1f1f1f1f1f1f1 ccccEE + F1fefeF1-/1212-/F2fifiF2 DDDD EEEiEiEiEi f2f2f2f2f2f2f2f2f2f2f2 ccccEE + b2b2-\F1fefeF1 -\F2fifiF2 DDDD EEEiEiEiEi f2f2f2f2f2f2f2f2f2f2f2 ccccEE-\b1b1 + b2b2-/F1fefeF1 -/F2fifiF2 DDDD EEEiEiEiEi f3f3f3f3f3f3f3f3f3f3f3 ccccEE-/b1b1 + F1fefeF1/-2121/-F2fifiF2 DDDD EEEiEiEiEi f3f3f3f3f3f3f3f3f3f3f3 ccccEE + F1F1F1F1\-2121\-F2F2F2F2 DDDD EEEiEiEiEi f4f4f4f4f4f4f4f4f4f4f4 ccccEE + EEEiEiEiEi f4f4f4f4f4f4f4f4f4f4f4 ccccEE + FiFiFiFi DiDiDiDi EE f5f5f5f5f5f5f5f5f5f5f5 ccccEE + Fi Fi Di Di EE f5f5f5f5f5f5f5f5f5f5f5 ccccEE + FiFiFiFi DiDiDiDi EE f6f6f6f6f6f6f6f6f6f6f6 ccccEE + EE f6f6f6f6f6f6f6f6f6f6f6 ccccEE + EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE +>>> + +# Macros for block type appearance. Macros are expanded in the 'block' +# lines below and can include other macros. +# +# Attributes are name=value pairs. +# Available names (and valid values); +# +# shape: {fifo, box, openBottom} +# stripDir: {vert, horiz} +# stripOrd: {RL, LR} +# decoder: {insts, lines, branch, dcache, counts} +# border: {thin, mid, thick} +# dataElement: <name> +# hideId: ({T, S, P, L, F, E})* +# name: <string> +# colours: ({U, B, -, E, R, F, r, w, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9})* +# type: {key, block} + +# macro fifo: shape=fifo stripDir=vert +macro fifo: shape=openBottom stripDir=horiz stripOrd=RL border=mid +macro fu: decoder=insts border=mid name="" nameLoc=left +# macro back: decoder=back border=mid dataElement=space name="" +macro prediction: decoder=branch border=mid dataElement=prediction name="" +macro forward: border=mid name="" +macro dcache: fifo decoder=dcache dataElement=addr +macro icache: fifo decoder=lines +macro cacheFrame: stripDir=vert decoder=frame blankStrips=5 \ + dataElement=in_tlb_mem +macro activity: decoder=counts shorten=2 border=mid +macro inputBuffer: name="inputBuffer" fifo +macro seqNum: decoder=counts border=mid +macro streamFrame: decoder=frame stripDir=vert dataElement=streamSeqNum +macro predictionFrame: decoder=frame stripDir=vert dataElement=predictionSeqNum + +# Block descriptions: +# description ::= <char>: <unit-name-in-trace> +# ( {<macro-name>, <name>=<value> )* +# name ::= ? alphanumeric name with dots ? +# value ::= "(<char-except-">)*", <char-except-' '>* } + +Fi: fetch2.inputBuffer inputBuffer decoder=lines +Di: decode.inputBuffer inputBuffer decoder=insts hideId=E +Ei: execute.inputBuffer inputBuffer stripDir=horiz decoder=insts border=mid +F1: fetch1 streamFrame blankStrips=11 name="Fetch1" +fe: fetch1 decoder=lines border=thin name="Line" +F2: fetch2 predictionFrame blankStrips=11 name="Fetch2" +fi: fetch2 decoder=insts border=thin name="Insts" +DD: decode decoder=insts name="Decode" +EE: execute streamFrame blankStrips=21 name="Execute" +cc: execute decoder=insts name="Commit" border=mid +12: f1ToF2 forward decoder=lines +21: f2ToF1 prediction +2D: f2ToD forward decoder=insts hideId=E +DE: dToE forward decoder=insts +b1: eToF1 forward decoder=branch +b2: eToF1 forward decoder=branch +IP: fetch1 cacheFrame name="Fetch queues" +LS: execute.lsq cacheFrame name="LSQ" +ir: fetch1.requests icache name="Requests" +it: fetch1.transfers icache name="Transfers" +dr: execute.lsq.requests dcache name="Requests" +dt: execute.lsq.transfers dcache name="Transfers" +sb: execute.lsq.storeBuffer dcache name="Store buffer" +KK: _ type=key colours="UB-ERFrw0123456789" +f0: execute.fu.0 fu shorten=2 name=Int +f1: execute.fu.1 fu shorten=2 name=Int +f2: execute.fu.2 fu shorten=2 name=Mul +f3: execute.fu.3 fu shorten=2 name=Div +f4: execute.fu.4 fu shorten=2 name=Float +f5: execute.fu.5 fu shorten=2 name=Mem +f6: execute.fu.6 fu shorten=2 name=Misc +iq: execute.inFlightInsts fifo decoder=insts name="inFlightInsts" +im: execute.inFUMemInsts fifo decoder=insts name="inFU..." +sc: execute.scoreboard name="scoreboard" decoder=indexedCounts \ + dataElement=busy border=mid name="scoreboard" strips=38 stripelems=3 +sa: activity dataElement=stages activity name="Stage activity" +ac: activity dataElement=activity decoder=counts border=mid name="Activity" diff --git a/util/minorview/model.py b/util/minorview/model.py new file mode 100644 index 000000000..a120f1f7c --- /dev/null +++ b/util/minorview/model.py @@ -0,0 +1,1109 @@ +# Copyright (c) 2013 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: Andrew Bardsley + +import parse +import colours +from colours import unknownColour +from point import Point +import re +import blobs +from time import time as wall_time +import os + +id_parts = "TSPLFE" + +all_ids = set(id_parts) +no_ids = set([]) + +class BlobDataSelect(object): + """Represents which data is displayed for Ided object""" + def __init__(self): + # Copy all_ids + self.ids = set(all_ids) + + def __and__(self, rhs): + """And for filtering""" + ret = BlobDataSelect() + ret.ids = self.ids.intersection(rhs.ids) + return ret + +class BlobVisualData(object): + """Super class for block data colouring""" + def to_striped_block(self, select): + """Return an array of colours to use for a striped block""" + return unknownColour + + def get_inst(self): + """Get an instruction Id (if any) from this data""" + return None + + def get_line(self): + """Get a line Id (if any) from this data""" + return None + + def __repr__(self): + return self.__class__.__name__ + '().from_string(' + \ + self.__str__() + ')' + + def __str__(self): + return '' + +class Id(BlobVisualData): + """A line or instruction id""" + def __init__(self): + self.isFault = False + self.threadId = 0 + self.streamSeqNum = 0 + self.predictionSeqNum = 0 + self.lineSeqNum = 0 + self.fetchSeqNum = 0 + self.execSeqNum = 0 + + def as_list(self): + return [self.threadId, self.streamSeqNum, self.predictionSeqNum, + self.lineSeqNum, self.fetchSeqNum, self.execSeqNum] + + def __cmp__(self, right): + return cmp(self.as_list(), right.as_list()) + + def from_string(self, string): + m = re.match('^(F;)?(\d+)/(\d+)\.(\d+)/(\d+)(/(\d+)(\.(\d+))?)?', + string) + + def seqnum_from_string(string): + if string is None: + return 0 + else: + return int(string) + + if m is None: + print 'Invalid Id string', string + else: + elems = m.groups() + + if elems[0] is not None: + self.isFault = True + else: + self.isFault = False + + self.threadId = seqnum_from_string(elems[1]) + self.streamSeqNum = seqnum_from_string(elems[2]) + self.predictionSeqNum = seqnum_from_string(elems[3]) + self.lineSeqNum = seqnum_from_string(elems[4]) + self.fetchSeqNum = seqnum_from_string(elems[6]) + self.execSeqNum = seqnum_from_string(elems[8]) + return self + + def get_inst(self): + if self.fetchSeqNum != 0: + return self + else: + return None + + def get_line(self): + return self + + def __str__(self): + """Returns the usual id T/S.P/L/F.E string""" + return ( + str(self.threadId) + '/' + + str(self.streamSeqNum) + '.' + + str(self.predictionSeqNum) + '/' + + str(self.lineSeqNum) + '/' + + str(self.fetchSeqNum) + '.' + + str(self.execSeqNum)) + + def to_striped_block(self, select): + ret = [] + + if self.isFault: + ret.append(colours.faultColour) + + if 'T' in select.ids: + ret.append(colours.number_to_colour(self.threadId)) + if 'S' in select.ids: + ret.append(colours.number_to_colour(self.streamSeqNum)) + if 'P' in select.ids: + ret.append(colours.number_to_colour(self.predictionSeqNum)) + if 'L' in select.ids: + ret.append(colours.number_to_colour(self.lineSeqNum)) + if self.fetchSeqNum != 0 and 'F' in select.ids: + ret.append(colours.number_to_colour(self.fetchSeqNum)) + if self.execSeqNum != 0 and 'E' in select.ids: + ret.append(colours.number_to_colour(self.execSeqNum)) + + if len(ret) == 0: + ret = [colours.unknownColour] + + if self.isFault: + ret.append(colours.faultColour) + + return ret + +class Branch(BlobVisualData): + """Branch data new stream and prediction sequence numbers, a branch + reason and a new PC""" + def __init__(self): + self.newStreamSeqNum = 0 + self.newPredictionSeqNum = 0 + self.newPC = 0 + self.reason = "NoBranch" + self.id = Id() + + def from_string(self, string): + m = re.match('^(\w+);(\d+)\.(\d+);([0-9a-fA-Fx]+);(.*)$', string) + + if m is not None: + self.reason, newStreamSeqNum, newPredictionSeqNum, \ + newPC, id = m.groups() + + self.newStreamSeqNum = int(newStreamSeqNum) + self.newPredictionSeqNum = int(newPredictionSeqNum) + self.newPC = int(newPC, 0) + self.id = special_view_decoder(Id)(id) + # self.branch = special_view_decoder(Branch)(branch) + else: + print "Bad Branch data:", string + return self + + def to_striped_block(self, select): + return [colours.number_to_colour(self.newStreamSeqNum), + colours.number_to_colour(self.newPredictionSeqNum), + colours.number_to_colour(self.newPC)] + +class Counts(BlobVisualData): + """Treat the input data as just a /-separated list of count values (or + just a single value)""" + def __init__(self): + self.counts = [] + + def from_string(self, string): + self.counts = map(int, re.split('/', string)) + return self + + def to_striped_block(self, select): + return map(colours.number_to_colour, self.counts) + +class Colour(BlobVisualData): + """A fixed colour block, used for special colour decoding""" + def __init__(self, colour): + self.colour = colour + + def to_striped_block(self, select): + return [self.colour] + +class DcacheAccess(BlobVisualData): + """Data cache accesses [RW];id""" + def __init__(self): + self.direc = 'R' + self.id = Id() + + def from_string(self, string): + self.direc, id = re.match('^([RW]);([^;]*);.*$', string).groups() + self.id.from_string(id) + return self + + def get_inst(self): + return self.id + + def to_striped_block(self, select): + if self.direc == 'R': + direc_colour = colours.readColour + elif self.direc == 'R': + direc_colour = colours.writeColour + else: + direc_colour = colours.errorColour + return [direc_colour] + self.id.to_striped_block(select) + +class ColourPattern(object): + """Super class for decoders that make 2D grids rather than just single + striped blocks""" + def elems(self): + return [] + + def to_striped_block(self, select): + return [[[colours.errorColour]]] + +def special_view_decoder(class_): + """Generate a decode function that checks for special character + arguments first (and generates a fixed colour) before building a + BlobVisualData of the given class""" + def decode(symbol): + if symbol in special_state_colours: + return Colour(special_state_colours[symbol]) + else: + return class_().from_string(symbol) + return decode + +class TwoDColours(ColourPattern): + """A 2D grid pattern decoder""" + def __init__(self, blockss): + self.blockss = blockss + + @classmethod + def decoder(class_, elemClass, dataName): + """Factory for making decoders for particular block types""" + def decode(pairs): + if dataName not in pairs: + print 'TwoDColours: no event data called:', \ + dataName, 'in:', pairs + return class_([[Colour(colours.errorColour)]]) + else: + parsed = parse.list_parser(pairs[dataName]) + return class_(parse.map2(special_view_decoder(elemClass), \ + parsed)) + return decode + + @classmethod + def indexed_decoder(class_, elemClass, dataName, picPairs): + """Factory for making decoders for particular block types but + where the list elements are pairs of (index, data) and + strip and stripelems counts are picked up from the pair + data on the decoder's picture file. This gives a 2D layout + of the values with index 0 at strip=0, elem=0 and index 1 + at strip=0, elem=1""" + def decode(pairs): + if dataName not in pairs: + print 'TwoDColours: no event data called:', \ + dataName, 'in:', pairs + return class_([[Colour(colours.errorColour)]]) + else: + strips = int(picPairs['strips']) + strip_elems = int(picPairs['stripelems']) + + raw_iv_pairs = pairs[dataName] + + parsed = parse.parse_indexed_list(raw_iv_pairs) + + array = [[Colour(colours.emptySlotColour) + for i in xrange(0, strip_elems)] + for j in xrange(0, strips)] + + for index, value in parsed: + try: + array[index % strips][index / strips] = \ + special_view_decoder(elemClass)(value) + except: + print "Element out of range strips: %d," \ + " stripelems %d, index: %d" % (strips, + strip_elems, index) + + # return class_(array) + return class_(array) + return decode + + def elems(self): + """Get a flat list of all elements""" + ret = [] + for blocks in self.blockss: + ret += blocks + return ret + + def to_striped_block(self, select): + return parse.map2(lambda d: d.to_striped_block(select), self.blockss) + +class FrameColours(ColourPattern): + """Decode to a 2D grid which has a single occupied row from the event + data and some blank rows forming a frame with the occupied row as a + 'title' coloured stripe""" + def __init__(self, block, numBlankSlots): + self.numBlankSlots = numBlankSlots + self.block = block + + @classmethod + def decoder(class_, elemClass, numBlankSlots, dataName): + """Factory for element type""" + def decode(pairs): + if dataName not in pairs: + print 'FrameColours: no event data called:', dataName, \ + 'in:', pairs + return class_([Colour(colours.errorColour)]) + else: + parsed = parse.list_parser(pairs[dataName]) + return class_(special_view_decoder(elemClass) + (parsed[0][0]), numBlankSlots) + return decode + + def elems(self): + return [self.block] + + def to_striped_block(self, select): + return ([[self.block.to_striped_block(select)]] + + (self.numBlankSlots * [[[colours.backgroundColour]]])) + +special_state_colours = { + 'U': colours.unknownColour, + 'B': colours.blockedColour, + '-': colours.bubbleColour, + '': colours.emptySlotColour, + 'E': colours.emptySlotColour, + 'R': colours.reservedSlotColour, + 'X': colours.errorColour, + 'F': colours.faultColour, + 'r': colours.readColour, + 'w': colours.writeColour + } + +special_state_names = { + 'U': '(U)nknown', + 'B': '(B)locked', + '-': '(-)Bubble', + '': '()Empty', + 'E': '(E)mpty', + 'R': '(R)eserved', + 'X': '(X)Error', + 'F': '(F)ault', + 'r': '(r)ead', + 'w': '(w)rite' + } + +special_state_chars = special_state_colours.keys() + +# The complete set of available block data types +decoder_element_classes = { + 'insts': Id, + 'lines': Id, + 'branch': Branch, + 'dcache': DcacheAccess, + 'counts': Counts + } + +indexed_decoder_element_classes = { + 'indexedCounts' : Counts + } + +def find_colour_decoder(stripSpace, decoderName, dataName, picPairs): + """Make a colour decoder from some picture file blob attributes""" + if decoderName == 'frame': + return FrameColours.decoder(Counts, stripSpace, dataName) + elif decoderName in decoder_element_classes: + return TwoDColours.decoder(decoder_element_classes[decoderName], + dataName) + elif decoderName in indexed_decoder_element_classes: + return TwoDColours.indexed_decoder( + indexed_decoder_element_classes[decoderName], dataName, picPairs) + else: + return None + +class IdedObj(object): + """An object identified by an Id carrying paired data. + The super class for Inst and Line""" + + def __init__(self, id, pairs={}): + self.id = id + self.pairs = pairs + + def __cmp__(self, right): + return cmp(self.id, right.id) + + def table_line(self): + """Represent the object as a list of table row data""" + return [] + + # FIXME, add a table column titles? + + def __repr__(self): + return ' '.join(self.table_line()) + +class Inst(IdedObj): + """A non-fault instruction""" + def __init__(self, id, disassembly, addr, pairs={}): + super(Inst,self).__init__(id, pairs) + if 'nextAddr' in pairs: + self.nextAddr = int(pairs['nextAddr'], 0) + del pairs['nextAddr'] + else: + self.nextAddr = None + self.disassembly = disassembly + self.addr = addr + + def table_line(self): + if self.nextAddr is not None: + addrStr = '0x%x->0x%x' % (self.addr, self.nextAddr) + else: + addrStr = '0x%x' % self.addr + ret = [addrStr, self.disassembly] + for name, value in self.pairs.iteritems(): + ret.append("%s=%s" % (name, str(value))) + return ret + +class InstFault(IdedObj): + """A fault instruction""" + def __init__(self, id, fault, addr, pairs={}): + super(InstFault,self).__init__(id, pairs) + self.fault = fault + self.addr = addr + + def table_line(self): + ret = ["0x%x" % self.addr, self.fault] + for name, value in self.pairs: + ret.append("%s=%s", name, str(value)) + return ret + +class Line(IdedObj): + """A fetched line""" + def __init__(self, id, vaddr, paddr, size, pairs={}): + super(Line,self).__init__(id, pairs) + self.vaddr = vaddr + self.paddr = paddr + self.size = size + + def table_line(self): + ret = ["0x%x/0x%x" % (self.vaddr, self.paddr), "%d" % self.size] + for name, value in self.pairs: + ret.append("%s=%s", name, str(value)) + return ret + +class LineFault(IdedObj): + """A faulting line""" + def __init__(self, id, fault, vaddr, pairs={}): + super(LineFault,self).__init__(id, pairs) + self.vaddr = vaddr + self.fault = fault + + def table_line(self): + ret = ["0x%x" % self.vaddr, self.fault] + for name, value in self.pairs: + ret.append("%s=%s", name, str(value)) + return ret + +class BlobEvent(object): + """Time event for a single blob""" + def __init__(self, unit, time, pairs = {}): + # blob's unit name + self.unit = unit + self.time = time + # dict of picChar (blob name) to visual data + self.visuals = {} + # Miscellaneous unparsed MinorTrace line data + self.pairs = pairs + # Non-MinorTrace debug printout for this unit at this time + self.comments = [] + + def find_ided_objects(self, model, picChar, includeInstLines): + """Find instructions/lines mentioned in the blob's event + data""" + ret = [] + if picChar in self.visuals: + blocks = self.visuals[picChar].elems() + def find_inst(data): + instId = data.get_inst() + lineId = data.get_line() + if instId is not None: + inst = model.find_inst(instId) + line = model.find_line(instId) + if inst is not None: + ret.append(inst) + if includeInstLines and line is not None: + ret.append(line) + elif lineId is not None: + line = model.find_line(lineId) + if line is not None: + ret.append(line) + map(find_inst, blocks) + return sorted(ret) + +class BlobModel(object): + """Model bringing together blob definitions and parsed events""" + def __init__(self, unitNamePrefix=''): + self.blobs = [] + self.unitNameToBlobs = {} + self.unitEvents = {} + self.clear_events() + self.picSize = Point(20,10) + self.lastTime = 0 + self.unitNamePrefix = unitNamePrefix + + def clear_events(self): + """Drop all events and times""" + self.lastTime = 0 + self.times = [] + self.insts = {} + self.lines = {} + self.numEvents = 0 + + for unit, events in self.unitEvents.iteritems(): + self.unitEvents[unit] = [] + + def add_blob(self, blob): + """Add a parsed blob to the model""" + self.blobs.append(blob) + if blob.unit not in self.unitNameToBlobs: + self.unitNameToBlobs[blob.unit] = [] + + self.unitNameToBlobs[blob.unit].append(blob) + + def add_inst(self, inst): + """Add a MinorInst instruction definition to the model""" + # Is this a non micro-op instruction. Microops (usually) get their + # fetchSeqNum == 0 varient stored first + macroop_key = (inst.id.fetchSeqNum, 0) + full_key = (inst.id.fetchSeqNum, inst.id.execSeqNum) + + if inst.id.execSeqNum != 0 and macroop_key not in self.insts: + self.insts[macroop_key] = inst + + self.insts[full_key] = inst + + def find_inst(self, id): + """Find an instruction either as a microop or macroop""" + macroop_key = (id.fetchSeqNum, 0) + full_key = (id.fetchSeqNum, id.execSeqNum) + + if full_key in self.insts: + return self.insts[full_key] + elif macroop_key in self.insts: + return self.insts[macroop_key] + else: + return None + + def add_line(self, line): + """Add a MinorLine line to the model""" + self.lines[line.id.lineSeqNum] = line + + def add_unit_event(self, event): + """Add a single event to the model. This must be an event at a + time >= the current maximum time""" + if event.unit in self.unitEvents: + events = self.unitEvents[event.unit] + if len(events) > 0 and events[len(events)-1].time > event.time: + print "Bad event ordering" + events.append(event) + self.numEvents += 1 + self.lastTime = max(self.lastTime, event.time) + + def extract_times(self): + """Extract a list of all the times from the seen events. Call after + reading events to give a safe index list to use for time indices""" + times = {} + for unitEvents in self.unitEvents.itervalues(): + for event in unitEvents: + times[event.time] = 1 + self.times = times.keys() + self.times.sort() + + def find_line(self, id): + """Find a line by id""" + key = id.lineSeqNum + return self.lines.get(key, None) + + def find_event_bisection(self, unit, time, events, + lower_index, upper_index): + """Find an event by binary search on time indices""" + while lower_index <= upper_index: + pivot = (upper_index + lower_index) / 2 + pivotEvent = events[pivot] + event_equal = (pivotEvent.time == time or + (pivotEvent.time < time and + (pivot == len(events) - 1 or + events[pivot + 1].time > time))) + + if event_equal: + return pivotEvent + elif time > pivotEvent.time: + if pivot == upper_index: + return None + else: + lower_index = pivot + 1 + elif time < pivotEvent.time: + if pivot == lower_index: + return None + else: + upper_index = pivot - 1 + else: + return None + return None + + def find_unit_event_by_time(self, unit, time): + """Find the last event for the given unit at time <= time""" + if unit in self.unitEvents: + events = self.unitEvents[unit] + ret = self.find_event_bisection(unit, time, events, + 0, len(events)-1) + + return ret + else: + return None + + def find_time_index(self, time): + """Find a time index close to the given time (where + times[return] <= time and times[return+1] > time""" + ret = 0 + lastIndex = len(self.times) - 1 + while ret < lastIndex and self.times[ret + 1] <= time: + ret += 1 + return ret + + def add_minor_inst(self, rest): + """Parse and add a MinorInst line to the model""" + pairs = parse.parse_pairs(rest) + other_pairs = dict(pairs) + + id = Id().from_string(pairs['id']) + del other_pairs['id'] + + addr = int(pairs['addr'], 0) + del other_pairs['addr'] + + if 'inst' in other_pairs: + del other_pairs['inst'] + + # Collapse unnecessary spaces in disassembly + disassembly = re.sub(' *', ' ', + re.sub('^ *', '', pairs['inst'])) + + inst = Inst(id, disassembly, addr, other_pairs) + self.add_inst(inst) + elif 'fault' in other_pairs: + del other_pairs['fault'] + + inst = InstFault(id, pairs['fault'], addr, other_pairs) + + self.add_inst(inst) + + def add_minor_line(self, rest): + """Parse and add a MinorLine line to the model""" + pairs = parse.parse_pairs(rest) + other_pairs = dict(pairs) + + id = Id().from_string(pairs['id']) + del other_pairs['id'] + + vaddr = int(pairs['vaddr'], 0) + del other_pairs['vaddr'] + + if 'paddr' in other_pairs: + del other_pairs['paddr'] + del other_pairs['size'] + paddr = int(pairs['paddr'], 0) + size = int(pairs['size'], 0) + + self.add_line(Line(id, + vaddr, paddr, size, other_pairs)) + elif 'fault' in other_pairs: + del other_pairs['fault'] + + self.add_line(LineFault(id, pairs['fault'], vaddr, other_pairs)) + + def load_events(self, file, startTime=0, endTime=None): + """Load an event file and add everything to this model""" + def update_comments(comments, time): + # Add a list of comments to an existing event, if there is one at + # the given time, or create a new, correctly-timed, event from + # the last event and attach the comments to that + for commentUnit, commentRest in comments: + event = self.find_unit_event_by_time(commentUnit, time) + # Find an event to which this comment can be attached + if event is None: + # No older event, make a new empty one + event = BlobEvent(commentUnit, time, {}) + self.add_unit_event(event) + elif event.time != time: + # Copy the old event and make a new one with the right + # time and comment + newEvent = BlobEvent(commentUnit, time, event.pairs) + newEvent.visuals = dict(event.visuals) + event = newEvent + self.add_unit_event(event) + event.comments.append(commentRest) + + self.clear_events() + + # A negative time will *always* be different from an event time + time = -1 + time_events = {} + last_time_lines = {} + minor_trace_line_count = 0 + comments = [] + + default_colour = [[colours.unknownColour]] + next_progress_print_event_count = 1000 + + if not os.access(file, os.R_OK): + print 'Can\'t open file', file + exit(1) + else: + print 'Opening file', file + + f = open(file) + + start_wall_time = wall_time() + + # Skip leading events + still_skipping = True + l = f.readline() + while l and still_skipping: + match = re.match('^\s*(\d+):', l) + if match is not None: + event_time = match.groups() + if int(event_time[0]) >= startTime: + still_skipping = False + else: + l = f.readline() + else: + l = f.readline() + + match_line_re = re.compile( + '^\s*(\d+):\s*([\w\.]+):\s*(Minor\w+:)?\s*(.*)$') + + # Parse each line of the events file, accumulating comments to be + # attached to MinorTrace events when the time changes + reached_end_time = False + while not reached_end_time and l: + match = match_line_re.match(l) + if match is not None: + event_time, unit, line_type, rest = match.groups() + event_time = int(event_time) + + unit = re.sub('^' + self.unitNamePrefix + '\.?(.*)$', + '\\1', unit) + + # When the time changes, resolve comments + if event_time != time: + if self.numEvents > next_progress_print_event_count: + print ('Parsed to time: %d' % event_time) + next_progress_print_event_count = ( + self.numEvents + 1000) + update_comments(comments, time) + comments = [] + time = event_time + + if line_type is None: + # Treat this line as just a 'comment' + comments.append((unit, rest)) + elif line_type == 'MinorTrace:': + minor_trace_line_count += 1 + + # Only insert this event if it's not the same as + # the last event we saw for this unit + if last_time_lines.get(unit, None) != rest: + event = BlobEvent(unit, event_time, {}) + pairs = parse.parse_pairs(rest) + event.pairs = pairs + + # Try to decode the colour data for this event + blobs = self.unitNameToBlobs.get(unit, []) + for blob in blobs: + if blob.visualDecoder is not None: + event.visuals[blob.picChar] = ( + blob.visualDecoder(pairs)) + + self.add_unit_event(event) + last_time_lines[unit] = rest + elif line_type == 'MinorInst:': + self.add_minor_inst(rest) + elif line_type == 'MinorLine:': + self.add_minor_line(rest) + + if endTime is not None and time > endTime: + reached_end_time = True + + l = f.readline() + + update_comments(comments, time) + self.extract_times() + f.close() + + end_wall_time = wall_time() + + print 'Total events:', minor_trace_line_count, 'unique events:', \ + self.numEvents + print 'Time to parse:', end_wall_time - start_wall_time + + def add_blob_picture(self, offset, pic, nameDict): + """Add a parsed ASCII-art pipeline markup to the model""" + pic_width = 0 + for line in pic: + pic_width = max(pic_width, len(line)) + pic_height = len(pic) + + # Number of horizontal characters per 'pixel'. Should be 2 + charsPerPixel = 2 + + # Clean up pic_width to a multiple of charsPerPixel + pic_width = (pic_width + charsPerPixel - 1) // 2 + + self.picSize = Point(pic_width, pic_height) + + def pic_at(point): + """Return the char pair at the given point. + Returns None for characters off the picture""" + x, y = point.to_pair() + x *= 2 + if y >= len(pic) or x >= len(pic[y]): + return None + else: + return pic[y][x:x + charsPerPixel] + + def clear_pic_at(point): + """Clear the chars at point so we don't trip over them again""" + line = pic[point.y] + x = point.x * charsPerPixel + pic[point.y] = line[0:x] + (' ' * charsPerPixel) + \ + line[x + charsPerPixel:] + + def skip_same_char(start, increment): + """Skip characters which match pic_at(start)""" + char = pic_at(start) + hunt = start + while pic_at(hunt) == char: + hunt += increment + return hunt + + def find_size(start): + """Find the size of a rectangle with top left hand corner at + start consisting of (at least) a -. shaped corner describing + the top right corner of a rectangle of the same char""" + char = pic_at(start) + hunt_x = skip_same_char(start, Point(1,0)) + hunt_y = skip_same_char(start, Point(0,1)) + off_bottom_right = (hunt_x * Point(1,0)) + (hunt_y * Point(0,1)) + return off_bottom_right - start + + def point_return(point): + """Carriage return, line feed""" + return Point(0, point.y + 1) + + def find_arrow(start): + """Find a simple 1-char wide arrow""" + + def body(endChar, contChar, direc): + arrow_point = start + arrow_point += Point(0, 1) + clear_pic_at(start) + while pic_at(arrow_point) == contChar: + clear_pic_at(arrow_point) + arrow_point += Point(0, 1) + + if pic_at(arrow_point) == endChar: + clear_pic_at(arrow_point) + self.add_blob(blobs.Arrow('_', start + offset, + direc = direc, + size = (Point(1, 1) + arrow_point - start))) + else: + print 'Bad arrow', start + + char = pic_at(start) + if char == '-\\': + body('-/', ' :', 'right') + elif char == '/-': + body('\\-', ': ', 'left') + + blank_chars = [' ', ' :', ': '] + + # Traverse the picture left to right, top to bottom to find blobs + seen_dict = {} + point = Point(0,0) + while pic_at(point) is not None: + while pic_at(point) is not None: + char = pic_at(point) + if char == '->': + self.add_blob(blobs.Arrow('_', point + offset, + direc = 'right')) + elif char == '<-': + self.add_blob(blobs.Arrow('_', point + offset, + direc = 'left')) + elif char == '-\\' or char == '/-': + find_arrow(point) + elif char in blank_chars: + pass + else: + if char not in seen_dict: + size = find_size(point) + topLeft = point + offset + if char not in nameDict: + # Unnamed blobs + self.add_blob(blobs.Block(char, + nameDict.get(char, '_'), + topLeft, size = size)) + else: + # Named blobs, set visual info. + blob = nameDict[char] + blob.size = size + blob.topLeft = topLeft + self.add_blob(blob) + seen_dict[char] = True + point = skip_same_char(point, Point(1,0)) + point = point_return(point) + + def load_picture(self, filename): + """Load a picture file into the model""" + def parse_blob_description(char, unit, macros, pairsList): + # Parse the name value pairs in a blob-describing line + def expand_macros(pairs, newPairs): + # Recursively expand macros + for name, value in newPairs: + if name in macros: + expand_macros(pairs, macros[name]) + else: + pairs[name] = value + return pairs + + pairs = expand_macros({}, pairsList) + + ret = None + + typ = pairs.get('type', 'block') + colour = colours.name_to_colour(pairs.get('colour', 'black')) + + if typ == 'key': + ret = blobs.Key(char, unit, Point(0,0), colour) + elif typ == 'block': + ret = blobs.Block(char, unit, Point(0,0), colour) + else: + print "Bad picture blog type:", typ + + if 'hideId' in pairs: + hide = pairs['hideId'] + ret.dataSelect.ids -= set(hide) + + if typ == 'block': + ret.displayName = pairs.get('name', unit) + ret.nameLoc = pairs.get('nameLoc', 'top') + ret.shape = pairs.get('shape', 'box') + ret.stripDir = pairs.get('stripDir', 'horiz') + ret.stripOrd = pairs.get('stripOrd', 'LR') + ret.blankStrips = int(pairs.get('blankStrips', '0')) + ret.shorten = int(pairs.get('shorten', '0')) + + if 'decoder' in pairs: + decoderName = pairs['decoder'] + dataElement = pairs.get('dataElement', decoderName) + + decoder = find_colour_decoder(ret.blankStrips, + decoderName, dataElement, pairs) + if decoder is not None: + ret.visualDecoder = decoder + else: + print 'Bad visualDecoder requested:', decoderName + + if 'border' in pairs: + border = pairs['border'] + if border == 'thin': + ret.border = 0.2 + elif border == 'mid': + ret.border = 0.5 + else: + ret.border = 1.0 + elif typ == 'key': + ret.colours = pairs.get('colours', ret.colours) + + return ret + + def line_is_comment(line): + """Returns true if a line starts with #, returns False + for lines which are None""" + return line is not None \ + and re.match('^\s*#', line) is not None + + def get_line(f): + """Get a line from file f extending that line if it ends in + '\' and dropping lines that start with '#'s""" + ret = f.readline() + + # Discard comment lines + while line_is_comment(ret): + ret = f.readline() + + if ret is not None: + extend_match = re.match('^(.*)\\\\$', ret) + + while extend_match is not None: + new_line = f.readline() + + if new_line is not None and not line_is_comment(new_line): + line_wo_backslash, = extend_match.groups() + ret = line_wo_backslash + new_line + extend_match = re.match('^(.*)\\\\$', ret) + else: + extend_match = None + + return ret + + # Macros are recursively expanded into name=value pairs + macros = {} + + if not os.access(filename, os.R_OK): + print 'Can\'t open file', filename + exit(1) + else: + print 'Opening file', filename + + f = open(filename) + l = get_line(f) + picture = [] + blob_char_dict = {} + + self.unitEvents = {} + self.clear_events() + + # Actually parse the file + in_picture = False + while l: + l = parse.remove_trailing_ws(l) + l = re.sub('#.*', '', l) + + if re.match("^\s*$", l) is not None: + pass + elif l == '<<<': + in_picture = True + elif l == '>>>': + in_picture = False + elif in_picture: + picture.append(re.sub('\s*$', '', l)) + else: + line_match = re.match( + '^([a-zA-Z0-9][a-zA-Z0-9]):\s+([\w.]+)\s*(.*)', l) + macro_match = re.match('macro\s+(\w+):(.*)', l) + + if macro_match is not None: + name, defn = macro_match.groups() + macros[name] = parse.parse_pairs_list(defn) + elif line_match is not None: + char, unit, pairs = line_match.groups() + blob = parse_blob_description(char, unit, macros, + parse.parse_pairs_list(pairs)) + blob_char_dict[char] = blob + # Setup the events structure + self.unitEvents[unit] = [] + else: + print 'Problem with Blob line:', l + + l = get_line(f) + + self.blobs = [] + self.add_blob_picture(Point(0,1), picture, blob_char_dict) diff --git a/util/minorview/parse.py b/util/minorview/parse.py new file mode 100644 index 000000000..352371428 --- /dev/null +++ b/util/minorview/parse.py @@ -0,0 +1,109 @@ +# Copyright (c) 2013 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: Andrew Bardsley + +import re + +def list_parser(names): + """Parse a list of elements, some of which might be one-level sublists + within parentheses, into a a list of lists of those elements. For + example: list_parser('(a,b),c') -> [['a', 'b'], 'c']""" + elems = re.split(',', names) + ret = [] + accum = [] + for elem in elems: + if re.search('^\((.*)\)$', elem): + accum.append(re.sub('^\((.*)\)', '\\1', elem)) + ret.append(accum) + accum = [] + elif re.search('^\(', elem): + accum.append(re.sub('^\(', '', elem)) + elif re.search('\)$', elem): + accum.append(re.sub('\)$', '', elem)) + ret.append(accum) + accum = [] + elif len(accum) != 0: + accum.append(elem) + else: + ret.append([elem]) + + if len(accum) > 0: + print 'Non matching brackets in', names + + return ret + +def map2(f, ls): + """map to a depth of 2. That is, given a list of lists, apply + f to those innermost elements """ + return map(lambda l: map(f, l), ls) + +def remove_trailing_ws(line): + return re.sub('\s*$', '', line) + +def remove_leading_and_trailing_ws(line): + return re.sub('\s*$', '', re.sub('^\s*', '', line)) + +def parse_pairs_list(pairString): + """parse a string like 'name=value name2=value2' into a + list of pairs of ('name', 'value') ...""" + ret = [] + pairs = re.finditer('(\w+)(=("[^"]*"|[^\s]*))?', pairString) + for pair in pairs: + name, rest, value = pair.groups() + if value is not None: + value = re.sub('^"(.*)"$', '\\1', value) + ret.append((name, value)) + else: + ret.append((name, '')) + return ret + +def parse_indexed_list(string): + """parse a string of the form "(index,value),(index,value)..." + into a list of index, value pairs""" + + ret = [] + pairs = list_parser(string) + for pair in pairs: + if len(pair) == 2: + index, value = pair + ret.append((int(index), value)) + + return ret + +def parse_pairs(pairString): + """parse a string like 'name=value name2=value2' into a + dictionary of {'name': 'value', 'name2': 'value2'} """ + return dict(parse_pairs_list(pairString)) diff --git a/util/minorview/point.py b/util/minorview/point.py new file mode 100644 index 000000000..75097e2c2 --- /dev/null +++ b/util/minorview/point.py @@ -0,0 +1,78 @@ +# Copyright (c) 2013 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: Andrew Bardsley + +class Point(object): + """2D point coordinates/size type""" + def __init__(self, x, y): + self.x = x + self.y = y + + def __add__(self, rhs): + return Point(self.x + rhs.x, self.y + rhs.y) + + def __sub__(self, rhs): + return Point(self.x - rhs.x, self.y - rhs.y) + + def __mul__(self, rhs): + return Point(self.x * rhs.x, self.y * rhs.y) + + def __div__(self, rhs): + return Point(float(self.x) / rhs.x, float(self.y) / rhs.y) + + def scale(self, factor): + return Point(self.x * factor, self.y * factor) + + def to_pair(self): + return (self.x, self.y) + + def __str__(self): + return "Point(%f,%f)" % (self.x, self.y) + + def __repr__(self): + return "Point(%f,%f)" % (self.x, self.y) + + def is_within_box(self, box): + """Is this point inside the (centre, size) box box""" + centre, size = box + half_size = size.scale(0.5) + top_left = centre - half_size + bottom_right = centre + half_size + return (top_left.x < self.x and + top_left.y < self.y and + bottom_right.x > self.x and + bottom_right.y > self.y) + diff --git a/util/minorview/view.py b/util/minorview/view.py new file mode 100644 index 000000000..8a9aaffea --- /dev/null +++ b/util/minorview/view.py @@ -0,0 +1,524 @@ +# Copyright (c) 2013 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: Andrew Bardsley + +import pygtk +pygtk.require('2.0') +import gtk +import gobject +import cairo +import re + +from point import Point +import parse +import colours +import model +from model import Id, BlobModel, BlobDataSelect, special_state_chars +import blobs + +class BlobView(object): + """The canvas view of the pipeline""" + def __init__(self, model): + # A unit blob will appear at size blobSize inside a space of + # size pitch. + self.blobSize = Point(45.0, 45.0) + self.pitch = Point(60.0, 60.0) + self.origin = Point(50.0, 50.0) + # Some common line definitions to cut down on arbitrary + # set_line_widths + self.thickLineWidth = 10.0 + self.thinLineWidth = 4.0 + self.midLineWidth = 6.0 + # The scale from the units of pitch to device units (nominally + # pixels for 1.0 to 1.0 + self.masterScale = Point(1.0,1.0) + self.model = model + self.fillColour = colours.emptySlotColour + self.timeIndex = 0 + self.time = 0 + self.positions = [] + self.controlbar = None + # The sequence number selector state + self.dataSelect = BlobDataSelect() + # Offset of this view's time from self.time used for miniviews + # This is actually an offset of the index into the array of times + # seen in the event file) + self.timeOffset = 0 + # Maximum view size for initial window mapping + self.initialHeight = 600.0 + + # Overlays are speech bubbles explaining blob data + self.overlays = [] + + self.da = gtk.DrawingArea() + def draw(arg1, arg2): + self.redraw() + self.da.connect('expose_event', draw) + + # Handy offsets from the blob size + self.blobIndent = (self.pitch - self.blobSize).scale(0.5) + self.blobIndentFactor = self.blobIndent / self.pitch + + def add_control_bar(self, controlbar): + """Add a BlobController to this view""" + self.controlbar = controlbar + + def draw_to_png(self, filename): + """Draw the view to a PNG file""" + surface = cairo.ImageSurface( + cairo.FORMAT_ARGB32, + self.da.get_allocation().width, + self.da.get_allocation().height) + cr = gtk.gdk.CairoContext(cairo.Context(surface)) + self.draw_to_cr(cr) + surface.write_to_png(filename) + + def draw_to_cr(self, cr): + """Draw to a given CairoContext""" + cr.set_source_color(colours.backgroundColour) + cr.set_line_width(self.thickLineWidth) + cr.paint() + cr.save() + cr.scale(*self.masterScale.to_pair()) + cr.translate(*self.origin.to_pair()) + + positions = [] # {} + + # Draw each blob + for blob in self.model.blobs: + blob_event = self.model.find_unit_event_by_time( + blob.unit, self.time) + + cr.save() + pos = blob.render(cr, self, blob_event, self.dataSelect, + self.time) + cr.restore() + if pos is not None: + (centre, size) = pos + positions.append((blob, centre, size)) + + # Draw all the overlays over the top + for overlay in self.overlays: + overlay.show(cr) + + cr.restore() + + return positions + + def redraw(self): + """Redraw the whole view""" + buffer = cairo.ImageSurface( + cairo.FORMAT_ARGB32, + self.da.get_allocation().width, + self.da.get_allocation().height) + + cr = gtk.gdk.CairoContext(cairo.Context(buffer)) + positions = self.draw_to_cr(cr) + + # Assume that blobs are in order for depth so we want to + # hit the frontmost blob first if we search by position + positions.reverse() + self.positions = positions + + # Paint the drawn buffer onto the DrawingArea + dacr = self.da.window.cairo_create() + dacr.set_source_surface(buffer, 0.0, 0.0) + dacr.paint() + + buffer.finish() + + def set_time_index(self, time): + """Set the time index for the view. A time index is an index into + the model's times array of seen event times""" + self.timeIndex = time + self.timeOffset + if len(self.model.times) != 0: + if self.timeIndex >= len(self.model.times): + self.time = self.model.times[len(self.model.times) - 1] + else: + self.time = self.model.times[self.timeIndex] + else: + self.time = 0 + + def get_pic_size(self): + """Return the size of ASCII-art picture of the pipeline scaled by + the blob pitch""" + return (self.origin + self.pitch * + (self.model.picSize + Point(1.0,1.0))) + + def set_da_size(self): + """Set the DrawingArea size after scaling""" + self.da.set_size_request(10 , int(self.initialHeight)) + +class BlobController(object): + """The controller bar for the viewer""" + def __init__(self, model, view, + defaultEventFile="", defaultPictureFile=""): + self.model = model + self.view = view + self.playTimer = None + self.filenameEntry = gtk.Entry() + self.filenameEntry.set_text(defaultEventFile) + self.pictureEntry = gtk.Entry() + self.pictureEntry.set_text(defaultPictureFile) + self.timeEntry = None + self.defaultEventFile = defaultEventFile + self.startTime = None + self.endTime = None + + self.otherViews = [] + + def make_bar(elems): + box = gtk.HBox(homogeneous=False, spacing=2) + box.set_border_width(2) + for widget, signal, handler in elems: + if signal is not None: + widget.connect(signal, handler) + box.pack_start(widget, False, True, 0) + return box + + self.timeEntry = gtk.Entry() + + t = gtk.ToggleButton('T') + t.set_active(False) + s = gtk.ToggleButton('S') + s.set_active(True) + p = gtk.ToggleButton('P') + p.set_active(True) + l = gtk.ToggleButton('L') + l.set_active(True) + f = gtk.ToggleButton('F') + f.set_active(True) + e = gtk.ToggleButton('E') + e.set_active(True) + + # Should really generate this from above + self.view.dataSelect.ids = set("SPLFE") + + self.bar = gtk.VBox() + self.bar.set_homogeneous(False) + + row1 = make_bar([ + (gtk.Button('Start'), 'clicked', self.time_start), + (gtk.Button('End'), 'clicked', self.time_end), + (gtk.Button('Back'), 'clicked', self.time_back), + (gtk.Button('Forward'), 'clicked', self.time_forward), + (gtk.Button('Play'), 'clicked', self.time_play), + (gtk.Button('Stop'), 'clicked', self.time_stop), + (self.timeEntry, 'activate', self.time_set), + (gtk.Label('Visible ids:'), None, None), + (t, 'clicked', self.toggle_id('T')), + (gtk.Label('/'), None, None), + (s, 'clicked', self.toggle_id('S')), + (gtk.Label('.'), None, None), + (p, 'clicked', self.toggle_id('P')), + (gtk.Label('/'), None, None), + (l, 'clicked', self.toggle_id('L')), + (gtk.Label('/'), None, None), + (f, 'clicked', self.toggle_id('F')), + (gtk.Label('.'), None, None), + (e, 'clicked', self.toggle_id('E')), + (self.filenameEntry, 'activate', self.load_events), + (gtk.Button('Reload'), 'clicked', self.load_events) + ]) + + self.bar.pack_start(row1, False, True, 0) + self.set_time_index(0) + + def toggle_id(self, id): + """One of the sequence number selector buttons has been toggled""" + def toggle(button): + if button.get_active(): + self.view.dataSelect.ids.add(id) + else: + self.view.dataSelect.ids.discard(id) + + # Always leave one thing visible + if len(self.view.dataSelect.ids) == 0: + self.view.dataSelect.ids.add(id) + button.set_active(True) + self.view.redraw() + return toggle + + def set_time_index(self, time): + """Set the time index in the view""" + self.view.set_time_index(time) + + for view in self.otherViews: + view.set_time_index(time) + view.redraw() + + self.timeEntry.set_text(str(self.view.time)) + + def time_start(self, button): + """Start pressed""" + self.set_time_index(0) + self.view.redraw() + + def time_end(self, button): + """End pressed""" + self.set_time_index(len(self.model.times) - 1) + self.view.redraw() + + def time_forward(self, button): + """Step forward pressed""" + self.set_time_index(min(self.view.timeIndex + 1, + len(self.model.times) - 1)) + self.view.redraw() + gtk.gdk.flush() + + def time_back(self, button): + """Step back pressed""" + self.set_time_index(max(self.view.timeIndex - 1, 0)) + self.view.redraw() + + def time_set(self, entry): + """Time dialogue changed. Need to find a suitable time + <= the entry's time""" + newTime = self.model.find_time_index(int(entry.get_text())) + self.set_time_index(newTime) + self.view.redraw() + + def time_step(self): + """Time step while playing""" + if not self.playTimer \ + or self.view.timeIndex == len(self.model.times) - 1: + self.time_stop(None) + return False + else: + self.time_forward(None) + return True + + def time_play(self, play): + """Automatically advance time every 100 ms""" + if not self.playTimer: + self.playTimer = gobject.timeout_add(100, self.time_step) + + def time_stop(self, play): + """Stop play pressed""" + if self.playTimer: + gobject.source_remove(self.playTimer) + self.playTimer = None + + def load_events(self, button): + """Reload events file""" + self.model.load_events(self.filenameEntry.get_text(), + startTime=self.startTime, endTime=self.endTime) + self.set_time_index(min(len(self.model.times) - 1, + self.view.timeIndex)) + self.view.redraw() + +class Overlay(object): + """An Overlay is a speech bubble explaining the data in a blob""" + def __init__(self, model, view, point, blob): + self.model = model + self.view = view + self.point = point + self.blob = blob + + def find_event(self): + """Find the event for a changing time and a fixed blob""" + return self.model.find_unit_event_by_time(self.blob.unit, + self.view.time) + + def show(self, cr): + """Draw the overlay""" + event = self.find_event() + + if event is None: + return + + insts = event.find_ided_objects(self.model, self.blob.picChar, + False) + + cr.set_line_width(self.view.thinLineWidth) + cr.translate(*(Point(0.0,0.0) - self.view.origin).to_pair()) + cr.scale(*(Point(1.0,1.0) / self.view.masterScale).to_pair()) + + # Get formatted data from the insts to format into a table + lines = list(inst.table_line() for inst in insts) + + text_size = 10.0 + cr.set_font_size(text_size) + + def text_width(str): + xb, yb, width, height, dx, dy = cr.text_extents(str) + return width + + # Find the maximum number of columns and the widths of each column + num_columns = 0 + for line in lines: + num_columns = max(num_columns, len(line)) + + widths = [0] * num_columns + for line in lines: + for i in xrange(0, len(line)): + widths[i] = max(widths[i], text_width(line[i])) + + # Calculate the size of the speech bubble + column_gap = 1 * text_size + id_width = 6 * text_size + total_width = sum(widths) + id_width + column_gap * (num_columns + 1) + gap_step = Point(1.0, 0.0).scale(column_gap) + + text_point = self.point + text_step = Point(0.0, text_size) + + size = Point(total_width, text_size * len(insts)) + + # Draw the speech bubble + blobs.speech_bubble(cr, self.point, size, text_size) + cr.set_source_color(colours.backgroundColour) + cr.fill_preserve() + cr.set_source_color(colours.black) + cr.stroke() + + text_point += Point(1.0,1.0).scale(2.0 * text_size) + + id_size = Point(id_width, text_size) + + # Draw the rows in the table + for i in xrange(0, len(insts)): + row_point = text_point + inst = insts[i] + line = lines[i] + blobs.striped_box(cr, row_point + id_size.scale(0.5), + id_size, inst.id.to_striped_block(self.view.dataSelect)) + cr.set_source_color(colours.black) + + row_point += Point(1.0, 0.0).scale(id_width) + row_point += text_step + # Draw the columns of each row + for j in xrange(0, len(line)): + row_point += gap_step + cr.move_to(*row_point.to_pair()) + cr.show_text(line[j]) + row_point += Point(1.0, 0.0).scale(widths[j]) + + text_point += text_step + +class BlobWindow(object): + """The top-level window and its mouse control""" + def __init__(self, model, view, controller): + self.model = model + self.view = view + self.controller = controller + self.controlbar = None + self.window = None + self.miniViewCount = 0 + + def add_control_bar(self, controlbar): + self.controlbar = controlbar + + def show_window(self): + self.window = gtk.Window() + + self.vbox = gtk.VBox() + self.vbox.set_homogeneous(False) + if self.controlbar: + self.vbox.pack_start(self.controlbar, False, True, 0) + self.vbox.add(self.view.da) + + if self.miniViewCount > 0: + self.miniViews = [] + self.miniViewHBox = gtk.HBox(homogeneous=True, spacing=2) + + # Draw mini views + for i in xrange(1, self.miniViewCount + 1): + miniView = BlobView(self.model) + miniView.set_time_index(0) + miniView.masterScale = Point(0.1, 0.1) + miniView.set_da_size() + miniView.timeOffset = i + 1 + self.miniViews.append(miniView) + self.miniViewHBox.pack_start(miniView.da, False, True, 0) + + self.controller.otherViews = self.miniViews + self.vbox.add(self.miniViewHBox) + + self.window.add(self.vbox) + + def show_event(picChar, event): + print '**** Comments for', event.unit, \ + 'at time', self.view.time + for name, value in event.pairs.iteritems(): + print name, '=', value + for comment in event.comments: + print comment + if picChar in event.visuals: + # blocks = event.visuals[picChar].elems() + print '**** Colour data' + objs = event.find_ided_objects(self.model, picChar, True) + for obj in objs: + print ' '.join(obj.table_line()) + + def clicked_da(da, b): + point = Point(b.x, b.y) + + overlay = None + for blob, centre, size in self.view.positions: + if point.is_within_box((centre, size)): + event = self.model.find_unit_event_by_time(blob.unit, + self.view.time) + if event is not None: + if overlay is None: + overlay = Overlay(self.model, self.view, point, + blob) + show_event(blob.picChar, event) + if overlay is not None: + self.view.overlays = [overlay] + else: + self.view.overlays = [] + + self.view.redraw() + + # Set initial size and event callbacks + self.view.set_da_size() + self.view.da.add_events(gtk.gdk.BUTTON_PRESS_MASK) + self.view.da.connect('button-press-event', clicked_da) + self.window.connect('destroy', lambda(widget): gtk.main_quit()) + + def resize(window, event): + """Resize DrawingArea to match new window size""" + size = Point(float(event.width), float(event.height)) + proportion = size / self.view.get_pic_size() + # Preserve aspect ratio + daScale = min(proportion.x, proportion.y) + self.view.masterScale = Point(daScale, daScale) + self.view.overlays = [] + + self.view.da.connect('configure-event', resize) + + self.window.show_all() |