diff --git a/src/main/scala/utility/ChiselTaggedTrace.scala b/src/main/scala/utility/ChiselTaggedTrace.scala index e722a4c..ce84a9e 100644 --- a/src/main/scala/utility/ChiselTaggedTrace.scala +++ b/src/main/scala/utility/ChiselTaggedTrace.scala @@ -24,7 +24,19 @@ import freechips.rocketchip.amba.ahb.AHBImpMaster.bundle trait HasDPICUtils extends BlackBox with HasBlackBoxInline { var moduleName: String = "" - def init(args: Bundle, negedge: Boolean = false, comb_output: Boolean = false) = { + + /** + * + * make module that call dpic function + * + * io must contains "clock, reset, en" + * + * @param args input the io + * @param negedge trigger on negedge clock + * @param comb_output if has output, use wire out not reg + * @param overrideFuncname override the dpic name, default is class name + */ + def init(args: Bundle, negedge: Boolean = false, comb_output: Boolean = false, overrideFuncname: String = "") { val field = args.elements.map(t => { val name = t._1 val tpes = t._2.getClass.getMethods.map(x => x.getName()).toList @@ -48,9 +60,9 @@ trait HasDPICUtils extends BlackBox with HasBlackBoxInline { throw new Exception } - val className = this.getClass().getSimpleName() - moduleName = className + "_DPIC_Helper" - val dpicFunc = lang.Character.toLowerCase(className.charAt(0)) + className.substring(1) + val dpic_name = if (overrideFuncname.isEmpty()) this.getClass().getSimpleName() else overrideFuncname + moduleName = dpic_name + "_DPIC_Helper" + val dpicFunc = lang.Character.toLowerCase(dpic_name.charAt(0)) + dpic_name.substring(1) val verilog = s""" |import "DPI-C" function ${if (has_out) "longint unsigned" else "void"} $dpicFunc diff --git a/src/main/scala/utility/LogUtils.scala b/src/main/scala/utility/LogUtils.scala index 9cfce44..11ad9a8 100644 --- a/src/main/scala/utility/LogUtils.scala +++ b/src/main/scala/utility/LogUtils.scala @@ -77,6 +77,7 @@ private[utility] trait XSLogTap { object XSLog extends XSLogTap { private val logInfos = ListBuffer.empty[LogPerfParam] private val callBacks = ListBuffer.empty[(LogPerfIO) => Unit] + private val callBacksWithClock = ListBuffer.empty[(LogPerfIO, Reset, Clock) => Unit] private val logModules = ListBuffer.empty[BaseModule] private def unpackPrintable(pable: Printable): (String, Seq[Data]) = { @@ -158,7 +159,9 @@ object XSLog extends XSLogTap { // As XSPerf depends on LogPerfIO, their apply will be buffered until collection // Register collect() method from Callers when first apply, then call that during collection def registerCaller(caller: LogPerfIO => Unit): Unit = callBacks += caller + def registerCallerWithClock(caller: (LogPerfIO, Reset, Clock) => Unit): Unit = callBacksWithClock += caller def invokeCaller(ctrl: LogPerfIO): Unit = callBacks.foreach(caller => caller(ctrl)) + def invokeCallerWithClock(ctrl: LogPerfIO, reset: Reset, clock: Clock) = callBacksWithClock.foreach(caller => caller(ctrl, reset, clock)) // Should only be called during firrtl phase(ChiselStage) // PathName can not be accessed until circuit elaboration @@ -205,6 +208,7 @@ private class LogPerfEndpoint()(implicit p: Parameters) extends Module { } // To collect deferred call from XSPerf/..., invoke all registered caller + XSLog.invokeCallerWithClock(io, reset, clock) XSLog.invokeCaller(io) // Group printfs with same cond to reduce system tasks for better thread schedule XSLog.tapInfos.groupBy(_.cond).values.foreach { infos => diff --git a/src/main/scala/utility/PerfCounterUtils.scala b/src/main/scala/utility/PerfCounterUtils.scala index b2d5ecb..8f5d3dc 100644 --- a/src/main/scala/utility/PerfCounterUtils.scala +++ b/src/main/scala/utility/PerfCounterUtils.scala @@ -75,7 +75,6 @@ object XSPerfAccumulate extends HasRegularPerfName with XSLogTap { val next_counter = WireInit(0.U(64.W)).suggestName(perfName + "Next") next_counter := counter + perfCnt counter := Mux(perfClean, 0.U, next_counter) - XSPerfPrint(curMod)(perfDump, p"$perfName, $next_counter\n") } } diff --git a/src/main/scala/utility/Xstatistics.scala b/src/main/scala/utility/Xstatistics.scala new file mode 100644 index 0000000..07decd1 --- /dev/null +++ b/src/main/scala/utility/Xstatistics.scala @@ -0,0 +1,409 @@ +/*************************************************************************************** +* Copyright (c) 2024-2026 Beijing Institute of Open Source Chip +* +* Utility is licensed under Mulan PSL v2. +* You can use this software according to the terms and conditions of the Mulan PSL v2. +* You may obtain a copy of Mulan PSL v2 at: +* http://license.coscl.org.cn/MulanPSL2 +* +* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, +* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, +* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. +* +* See the Mulan PSL v2 for more details. +***************************************************************************************/ + +package utility.Xstatistics + +import chisel3._ +import chisel3.util.HasBlackBoxInline +import scala.collection.mutable.ArrayBuffer +import utility.HasDPICUtils +import freechips.rocketchip.diplomacy.BufferParams.default +import utility.FileRegisters +import scala.collection.mutable.ListBuffer +import utility.XSLog +import utility.LogPerfIO +import org.chipsalliance.cde.config.Parameters +import scala.util + +trait NodeExpr { + def +(b: NodeExpr) = { + ConstExpr("(" + exprStr() + "+" + b.exprStr() + ")") + } + def -(b: NodeExpr) = { + ConstExpr("(" + exprStr() + "-" + b.exprStr() + ")") + } + def *(b: NodeExpr) = { + ConstExpr("(" + exprStr() + "*" + b.exprStr() + ")") + } + def /(b: NodeExpr) = { + ConstExpr("(" + "(double)" + exprStr() + "/" + b.exprStr() + ")") + } + + def funcWrapping(mathFunc: String, argmap: String = "x") { + val arg = argmap.replace("x", exprStr()) + ConstExpr(mathFunc + s"(${arg})") + } + + // virtual: + def exprStr() = this match { + case node: Node => node.varname() + case _ => ??? + } +} + +class ConstExpr(private val express: String) extends NodeExpr { + override def exprStr() = express +} + +abstract class Node(val origin_name: String, val descript: String) { + judgeName(origin_name) + private val curModule = chisel3.XSCompatibility.currentModule.getOrElse(chisel3.XSCompatibility.currentModule.get) + private val full_name = "Xstat" + curModule.hashCode.toHexString + "__" + origin_name + protected val dpic_func = s"dpic_${varname()}" + XstatsMgr.register(this) + + private def judgeName(perfName: String): Unit = { + val regular = """(\w+)""".r + perfName match { + case regular(_) => + case _ => { + throw new Exception("Statistics: " + perfName + " is not '\\w+' regular") + } + } + } + + def varname() = full_name + // modulePath only can be called after chisel elaborate + private def modulePath() = curModule.pathName + protected def dumpFormat(name: String, v: String) ="\"" + modulePath + "." + name + "\\t\\t\\t\" << " + v + s" << \"\\t\\t\\t#${descript}\\n\"" + + // virtual: + def getVarDef() = s"uint64_t ${full_name} = 0;" + // return the cpp code of dump and reset + def getDumpingAndReset(): (String, String) = { + (s"dout << ${dumpFormat(origin_name, varname())};", s"${varname()} = 0;") + } + def getDPIC(): String = "" +} + +class Scalar(name: String, desc: String) extends Node(name, desc) with NodeExpr { + private class ScalarModule(name: String) extends HasDPICUtils { + val io = IO(new Bundle{ + val clock = Input(Clock()) + val reset = Input(Reset()) + val en = Input(Bool()) + val n = Input(UInt(64.W)) + }) + init(io, false, false, name) + } + + def sample(n: UInt, clock: Clock) { + val m = Module(new ScalarModule(dpic_func)) + m.io.clock := clock + m.io.reset := false.B + m.io.en := true.B + m.io.n := n + } + + def sample(n: UInt, en: Bool, clock: Clock) { + val m = Module(new ScalarModule(dpic_func)) + m.io.clock := clock + m.io.reset := false.B + m.io.en := en + m.io.n := n + } + + override def getDPIC() = { + s""" + |void ${dpic_func}(uint64_t val) { + | ${varname()} += val; + |} + """.stripMargin + } +} + +class Vector(n: Int, name: String, desc: String) extends Node(name, desc) { + val size = n + protected var subnames = new Array[String](n) + + subnames.zipWithIndex.foreach{ case (s, i) => + subnames(i) = i.toString() + } + + def apply(i : Int) = { + if (i >= size) { + throw new Exception(s"Statistics: ${varname()} out of range") + } + ConstExpr(varname() + s"[${i}]") + } + + def sum() = { + ConstExpr(s"std::accumulate(${varname()}.begin(), ${varname()}.end(), 0)") + } + + def maxCount() = { + ConstExpr(s"*std::max_element(${varname()}.begin(), ${varname()}.end());") + } + + def minCount() = { + ConstExpr(s"*std::min_element(${varname()}.begin(), ${varname()}.end());") + } + + def setSubname(name: Array[String]) { + require(subnames.size == name.size) + subnames = name + } + + private class VectorModule(name: String) extends HasDPICUtils { + val io = IO(new Bundle{ + val clock = Input(Clock()) + val reset = Input(Reset()) + val en = Input(Bool()) + val i = Input(UInt(64.W)) + val n = Input(UInt(64.W)) + }) + init(io, false, false, name) + } + + /** + * counter[i] += n + * + * @param i the input value ( < size or <= max) + * @param n the count of value + * @param en sample if enable + */ + def sample(i: UInt, n: UInt, en: Bool, clock: Clock) = { + val m = Module(new VectorModule(dpic_func)) + m.io.clock := clock + m.io.reset := false.B + m.io.en := en + m.io.i := i + m.io.n := n + } + + /** + * counter[i]++ + * + * @param i the input value ( < size or <= max) + */ + def sample(i: UInt, clock: Clock) = { + val m = Module(new VectorModule(dpic_func)) + m.io.clock := clock + m.io.reset := false.B + m.io.en := true.B + m.io.i := i + m.io.n := 1.U + } + + override def getVarDef() = s"std::array ${varname()};" + + override def getDumpingAndReset() = { + val dumpstr = s"${ + (for (i <- 0 until size) yield (origin_name + "::" + subnames(i), varname() + s"[${i}]")).map{case (n, v) => + s"dout << ${dumpFormat(n, v)};\n" + }.reduce(_ + _) + }" + val resetstr = s"std::fill(${varname()}.begin(), ${varname()}.end(), 0);" + (dumpstr, resetstr) + } + + override def getDPIC() = { + s""" + |void ${dpic_func}(uint64_t index, uint64_t val) { + | if (index < ${size}) { + | ${varname()}[index] += val; + | } + |} + """.stripMargin + } +} + +class Distribution(val min: Int, val max: Int, val bkt: Int, name: String, desc: String) extends Vector(((max + 1 - min).toFloat / bkt).ceil.round, name, desc) { + // couner <- [min, max], group by bkt + private val total = varname() + "_total" + private val overflows = varname() + "_overflows" + private val minvalue = varname() + "_min_value" + private val maxvalue = varname() + "_max_value" + + require(bkt > 0) + require((max - min) / bkt < size) + for (i <- 0 until size) { + // [low, up] + val low = i * bkt + min + var up = (i + 1) * bkt + min - 1 + up = math.min(up, max) + subnames(i) = s"${low}-${up}" + } + + override def sum() = ConstExpr(total) + def mean() = ConstExpr(s"((double)${total} / (${super.sum().exprStr()} + ${overflows}))") + def minValue() = ConstExpr(minvalue) + def maxValue() = ConstExpr(maxvalue) + + override def getVarDef() = { + val s = super.getVarDef() + s + s""" + |uint64_t ${total} = 0; + |uint64_t ${overflows} = 0; + |uint64_t ${minvalue} = 0; + |uint64_t ${maxvalue} = 0; + """.stripMargin + } + override def getDumpingAndReset() = { + var (dumpstr, resetstr) = super.getDumpingAndReset() + dumpstr += s""" + |dout << ${dumpFormat(origin_name + "::" + "total", total)}; + |dout << ${dumpFormat(origin_name + "::" + "mean", mean().exprStr())}; + |dout << ${dumpFormat(origin_name + "::" + "overflows", overflows)}; + |dout << ${dumpFormat(origin_name + "::" + "min_value", minvalue)}; + |dout << ${dumpFormat(origin_name + "::" + "max_value", maxvalue)}; + """.stripMargin + resetstr += s""" + |${total} = 0; + |${overflows} = 0; + |${minvalue} = 0; + |${maxvalue} = 0; + """.stripMargin + (dumpstr, resetstr) + } + override def getDPIC() = { + s""" + |void ${dpic_func}(uint64_t index, uint64_t val) { + | if (index <= ${max} && index >= ${min}) { + | unsigned t = (unsigned)(std::floor(index - ${min}) / ${bkt}); + | ${varname()}[t] += val; + | } else { + | ${overflows} += val; + | } + | ${total} += index * val; + | ${minvalue} = std::min(index, ${minvalue}); + | ${maxvalue} = std::max(index, ${maxvalue}); + |} + """.stripMargin + } +} + +class Formula(name: String, desc: String) extends Node(name, desc) with NodeExpr { + var express: String = "" + def :=(expr: NodeExpr) { + express = expr.exprStr() + } + + override def getVarDef() = "" + override def getDumpingAndReset() = { + (s"dout << ${dumpFormat(origin_name, exprStr())};", "") + } + override def exprStr() = express +} + +// anonymous counter, no need to dump +class AnoScalar[T <: Node] extends Scalar(util.Random.alphanumeric.take(8).mkString, "") { + override def getDumpingAndReset() = ("", super.getDumpingAndReset()._2) +} + +object ConstExpr { + def apply(expr: String) = new ConstExpr(expr) +} + +object Scalar { + def apply(name: String, desc: String) = new Scalar(name, desc) +} + +object Vector { + def apply(n:Int, name: String, desc: String) = new Vector(n, name, desc) +} + +object Distribution { + def apply(min: Int, max: Int, bkt: Int, name: String, desc: String) = new Distribution(min, max, bkt, name, desc) +} + +object Formula { + def apply(node: NodeExpr, name: String, desc: String) = { + val t = new Formula(name, desc) + t := node + t + } +} + +object AnoScalar { + def apply() = new AnoScalar +} + +object XstatsMgr { + private val counters = new ListBuffer[Node] + XSLog.registerCallerWithClock(collect) + + def addToFileRegisters { + FileRegisters.add("xs_statistics.cpp", getCpp()) + } + + def register(n: Node) { + val conflict = counters.exists(_.varname() == n.varname()) + if (conflict) { + throw new Exception(s"Statistics: ${n.varname()} has already exists") + } + counters.addOne(n) + } + + private def getCpp() = { + val cppvars: StringBuilder = new StringBuilder + val dumpstr: StringBuilder = new StringBuilder + val resetstr: StringBuilder = new StringBuilder + val dpicfuncs: StringBuilder = new StringBuilder + + counters.foreach{c => + cppvars ++= c.getVarDef() + "\n" + val t = c.getDumpingAndReset() + dumpstr ++= t._1 + "\n" + resetstr ++= t._2 + "\n" + dpicfuncs ++= c.getDPIC() + "\n" + } + + s""" + |#include + |#include + |#include + |#include + |#include + |#include + |#include + |#include + | + |${cppvars} + | + |extern "C" { + |// call this function when dumping + |void dpic_xs_statistics_dump() { + |auto dout = std::stringstream(); + |dout << "[********** start dump Xstatistics **********]\\n"; + |${dumpstr} + |std::cerr << dout.str(); + | + |${resetstr} + |} + | + |${dpicfuncs} + | + |} + | + """.stripMargin + } + + private class XstatsDump extends HasDPICUtils { + val io = IO(new Bundle{ + val clock = Input(Clock()) + val reset = Input(Reset()) + val en = Input(Bool()) + }) + init(io, false, false, "dpic_xs_statistics_dump") + } + + def collect(ctrl: LogPerfIO, reset: Reset, clock: Clock) { + val t = Module(new XstatsDump) + t.io.clock := clock + t.io.reset := reset + t.io.en := ctrl.dump && ctrl.logEnable + } +} \ No newline at end of file