Skip to content

Commit c8c3a83

Browse files
committed
feat(Xstatistics): powerful perf counter
1 parent fcb8223 commit c8c3a83

File tree

2 files changed

+347
-4
lines changed

2 files changed

+347
-4
lines changed

src/main/scala/utility/ChiselTaggedTrace.scala

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,19 @@ import freechips.rocketchip.amba.ahb.AHBImpMaster.bundle
2424

2525
trait HasDPICUtils extends BlackBox with HasBlackBoxInline {
2626
var moduleName: String = ""
27-
def init(args: Bundle, negedge: Boolean = false, comb_output: Boolean = false) = {
27+
28+
/**
29+
*
30+
* make module that call dpic function
31+
*
32+
* io must contains "clock, reset, en"
33+
*
34+
* @param args input the io
35+
* @param negedge trigger on negedge clock
36+
* @param comb_output if has output, use wire out not reg
37+
* @param overrideFuncname override the dpic name, default is class name
38+
*/
39+
def init(args: Bundle, negedge: Boolean = false, comb_output: Boolean = false, overrideFuncname: String = "") {
2840
val field = args.elements.map(t => {
2941
val name = t._1
3042
val tpes = t._2.getClass.getMethods.map(x => x.getName()).toList
@@ -48,9 +60,9 @@ trait HasDPICUtils extends BlackBox with HasBlackBoxInline {
4860
throw new Exception
4961
}
5062

51-
val className = this.getClass().getSimpleName()
52-
moduleName = className + "_DPIC_Helper"
53-
val dpicFunc = lang.Character.toLowerCase(className.charAt(0)) + className.substring(1)
63+
val dpic_name = if (overrideFuncname.isEmpty()) this.getClass().getSimpleName() else overrideFuncname
64+
moduleName = dpic_name + "_DPIC_Helper"
65+
val dpicFunc = lang.Character.toLowerCase(dpic_name.charAt(0)) + dpic_name.substring(1)
5466
val verilog =
5567
s"""
5668
|import "DPI-C" function ${if (has_out) "longint unsigned" else "void"} $dpicFunc
Lines changed: 331 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,331 @@
1+
/***************************************************************************************
2+
* Copyright (c) 2024-2026 Beijing Institute of Open Source Chip
3+
*
4+
* Utility is licensed under Mulan PSL v2.
5+
* You can use this software according to the terms and conditions of the Mulan PSL v2.
6+
* You may obtain a copy of Mulan PSL v2 at:
7+
* http://license.coscl.org.cn/MulanPSL2
8+
*
9+
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
10+
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
11+
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
12+
*
13+
* See the Mulan PSL v2 for more details.
14+
***************************************************************************************/
15+
16+
package utility.Xstatistics
17+
18+
import chisel3._
19+
import chisel3.util.HasBlackBoxInline
20+
import scala.collection.mutable.ArrayBuffer
21+
import utility.HasDPICUtils
22+
import freechips.rocketchip.diplomacy.BufferParams.default
23+
import utility.FileRegisters
24+
import scala.collection.mutable.ListBuffer
25+
26+
trait NodeExpr {
27+
def +(b: NodeExpr) = {
28+
new ConstExpr("(" + exprStr() + "+" + b.exprStr() + ")")
29+
}
30+
def -(b: NodeExpr) = {
31+
new ConstExpr("(" + exprStr() + "-" + b.exprStr() + ")")
32+
}
33+
def *(b: NodeExpr) = {
34+
new ConstExpr("(" + exprStr() + "*" + b.exprStr() + ")")
35+
}
36+
def /(b: NodeExpr) = {
37+
new ConstExpr("(" + "(double)" + exprStr() + "/" + b.exprStr() + ")")
38+
}
39+
40+
def funcWrapping(mathFunc: String) {
41+
new ConstExpr(mathFunc + s"(${exprStr()})")
42+
}
43+
44+
// virtual:
45+
def exprStr() = this match {
46+
case node: Node => node.varname()
47+
case _ => ???
48+
}
49+
}
50+
51+
class ConstExpr(val express: String) extends NodeExpr {
52+
override def exprStr() = express
53+
}
54+
55+
abstract class Node(val origin_name: String, val descript: String) {
56+
judgeName(origin_name)
57+
private val full_name = "xs_statistics_" + origin_name
58+
protected val dpic_func = s"dpic_${varname()}"
59+
val modulePath = chisel3.XSCompatibility.currentModule.getOrElse(chisel3.XSCompatibility.currentModule.get).toString()
60+
61+
private def judgeName(perfName: String): Unit = {
62+
val regular = """(\w+)""".r
63+
perfName match {
64+
case regular(_) =>
65+
case _ => {
66+
throw new Exception("Statistics: " + perfName + " is not '\\w+' regular")
67+
}
68+
}
69+
}
70+
71+
Xstatistics.register(this)
72+
73+
def varname() = full_name
74+
75+
def dumpFormat(name: String, v: String) ="\"" + modulePath + "." + name + "\\t\\t\" << " + v + s" << \"\\t\\t#${descript}\\n\""
76+
// virtual:
77+
def getVarDef() = s"uint64_t ${full_name} = 0;"
78+
def getDumpingAndReset() = {
79+
s"""
80+
|dout << ${dumpFormat(origin_name, varname())};
81+
|${varname()} = 0; // reset after dump
82+
""".stripMargin
83+
}
84+
def getDPIC(): String = ""
85+
}
86+
87+
class Scalar(name: String, desc: String) extends Node(name, desc) with NodeExpr {
88+
private class ScalarModule(name: String) extends HasDPICUtils {
89+
val io = IO(new Bundle{
90+
val clock = Input(Clock())
91+
val reset = Input(Reset())
92+
val en = Input(Bool())
93+
val n = Input(UInt(64.W))
94+
})
95+
init(io, false, false, name)
96+
}
97+
98+
def sample(n: UInt, en: Bool, clock: Clock) {
99+
val m = Module(new ScalarModule(dpic_func))
100+
m.io.clock := clock
101+
m.io.reset := false.B
102+
m.io.en := en
103+
m.io.n := n
104+
}
105+
106+
override def getDPIC() = {
107+
s"""
108+
|void ${dpic_func}(uint64_t val) {
109+
| ${varname()} += val;
110+
|}
111+
""".stripMargin
112+
}
113+
}
114+
115+
class Vector(n: Int, name: String, desc: String, val use_distr: Boolean = false) extends Node(name, desc) {
116+
private val total = varname() + "_total"
117+
private val overflows = varname() + "_overflows"
118+
private val minvalue = varname() + "_min_value"
119+
private val maxvalue = varname() + "_max_value"
120+
private var subnames = new Array[String](n)
121+
private val size = n
122+
private var min = 0;
123+
private var max = size - 1
124+
private var bkt = 1
125+
126+
subnames.zipWithIndex.foreach{ case (s, i) =>
127+
subnames(i) = i.toString()
128+
}
129+
130+
def apply(i : Int) = {
131+
if (i >= size) {
132+
throw new Exception(s"Statistics: ${varname()} out of range")
133+
}
134+
new ConstExpr(varname() + s"[${i}]")
135+
}
136+
137+
def sum() = {
138+
new ConstExpr(s"std::accumulate(${varname()}.begin(), ${varname()}.end(), 0)")
139+
}
140+
141+
def maxCount() = {
142+
new ConstExpr(s"*std::max_element(${varname()}.begin(), ${varname()}.end());")
143+
}
144+
145+
def minCount() = {
146+
new ConstExpr(s"*std::min_element(${varname()}.begin(), ${varname()}.end());")
147+
}
148+
149+
def len() = size
150+
151+
def setSubname(name: Array[String]) {
152+
require(subnames.size == name.size)
153+
subnames = name
154+
}
155+
156+
protected def setDist(min: Int, max: Int, bkt: Int) {
157+
// counter <- [min, max]
158+
require(use_distr)
159+
require((max - min) / bkt <= size)
160+
this.min = min
161+
this.max = max
162+
this.bkt = bkt
163+
for (i <- 0 until size) {
164+
// [low, up]
165+
val low = i * bkt + min
166+
var up = (i + 1) * bkt + min - 1
167+
up = math.min(up, max)
168+
subnames(i) = s"${low}-${up}"
169+
}
170+
}
171+
172+
private class VectorModule(name: String) extends HasDPICUtils {
173+
val io = IO(new Bundle{
174+
val clock = Input(Clock())
175+
val reset = Input(Reset())
176+
val en = Input(Bool())
177+
val i = Input(UInt(64.W))
178+
val n = Input(UInt(64.W))
179+
})
180+
init(io, false, false, name)
181+
}
182+
183+
def sample(i: UInt, n: UInt, en: Bool, clock: Clock) = {
184+
val m = Module(new VectorModule(dpic_func))
185+
m.io.clock := clock
186+
m.io.reset := false.B
187+
m.io.en := en
188+
m.io.i := i
189+
m.io.n := n
190+
}
191+
192+
override def getVarDef() = {
193+
val s = s"""
194+
|std::array<uint64_t, ${size}> ${varname()};
195+
""".stripMargin
196+
if (use_distr) {
197+
s + s"""
198+
|uint64_t ${total} = 0;
199+
|uint64_t ${overflows} = 0;
200+
|uint64_t ${minvalue} = 0;
201+
|uint64_t ${maxvalue} = 0;
202+
""".stripMargin
203+
} else {
204+
s
205+
}
206+
}
207+
override def getDumpingAndReset() = {
208+
val s = s"""
209+
|${
210+
(for (i <- 0 until size) yield (origin_name + "::" + subnames(i), varname() + s"[${i}]")).map{case (n, v) =>
211+
s"dout << ${dumpFormat(n, v)};\n"
212+
}.reduce(_ + _)
213+
}
214+
|std::fill(${varname()}.begin(), ${varname()}.end(), 0);
215+
""".stripMargin
216+
if (use_distr) {
217+
s + s"""
218+
|dout << ${dumpFormat(origin_name + "::" + "total", total)};
219+
|dout << ${dumpFormat(origin_name + "::" + "overflows", overflows)};
220+
|dout << ${dumpFormat(origin_name + "::" + "min_value", minvalue)};
221+
|dout << ${dumpFormat(origin_name + "::" + "max_value", maxvalue)};
222+
|${total} = 0;
223+
|${overflows} = 0;
224+
|${minvalue} = 0;
225+
|${maxvalue} = 0;
226+
""".stripMargin
227+
} else {
228+
s
229+
}
230+
}
231+
232+
override def getDPIC() = {
233+
if (use_distr) {
234+
s"""
235+
|void ${dpic_func}(uint64_t index, uint64_t val) {
236+
| if (index <= ${max} && index >= ${min}) {
237+
| index = (uint64_t)(std::floor(index - ${min}) / ${bkt});
238+
| ${varname()}[index] += val;
239+
| } else {
240+
| ${overflows}++;
241+
| }
242+
| ${total} += index;
243+
| ${minvalue} = std::min(index, ${minvalue});
244+
| ${maxvalue} = std::max(index, ${maxvalue});
245+
|}
246+
""".stripMargin
247+
} else {
248+
s"""
249+
|void ${dpic_func}(uint64_t index, uint64_t val) {
250+
| if (index < ${size}) {
251+
| ${varname()}[index] += val;
252+
| }
253+
|}
254+
""".stripMargin
255+
}
256+
}
257+
}
258+
259+
class Distribution(min: Int, max: Int, bkt: Int, name: String, desc: String) extends Vector(((max + 1 - min).toFloat / bkt).ceil.round, name, desc, true)
260+
{
261+
// couner <- [min, max], group by bkt
262+
require(bkt > 0)
263+
setDist(min, max, bkt)
264+
}
265+
266+
class Formula(name: String, desc: String) extends Node(name, desc) with NodeExpr {
267+
var express: String = ""
268+
def :=(expr: NodeExpr) {
269+
express = expr.exprStr()
270+
}
271+
272+
override def getVarDef() = ""
273+
override def getDumpingAndReset() = {
274+
s"""
275+
|dout << ${dumpFormat(origin_name, exprStr())};
276+
""".stripMargin
277+
}
278+
override def exprStr() = express
279+
}
280+
281+
282+
object Xstatistics {
283+
val counters = new ListBuffer[Node]
284+
def register(n: Node) {
285+
val conflict = counters.exists(_.varname() == n.varname())
286+
if (conflict) {
287+
throw new Exception(s"Statistics: ${n.varname()} has already exists")
288+
}
289+
counters.addOne(n)
290+
}
291+
292+
def getCpp() = {
293+
val cppvars: StringBuilder = new StringBuilder
294+
val dumpstr: StringBuilder = new StringBuilder
295+
val dpicfuncs: StringBuilder = new StringBuilder
296+
297+
dpicfuncs ++= "extern \"C\"{\n"
298+
counters.foreach{c =>
299+
cppvars ++= c.getVarDef() + "\n"
300+
dumpstr ++= c.getDumpingAndReset() + "\n"
301+
dpicfuncs ++= c.getDPIC() + "\n"
302+
}
303+
dpicfuncs ++= "}\n"
304+
305+
306+
s"""
307+
|#include <cstdint>
308+
|#include <array>
309+
|#include <string>
310+
|#include <algorithm>
311+
|#include <numeric>
312+
|#include <cmath>
313+
|#include <iostream>
314+
|
315+
|${cppvars}
316+
|
317+
|// call this function when dumping
318+
|void xs_statistics_dump() {
319+
|auto& dout = std::cerr;
320+
|${dumpstr}
321+
|}
322+
|
323+
|${dpicfuncs}
324+
|
325+
""".stripMargin
326+
}
327+
328+
def addToFileRegisters {
329+
FileRegisters.add("xs_statistics.cpp", getCpp())
330+
}
331+
}

0 commit comments

Comments
 (0)