Rocket-Chip-Hardware_Performance_Monitor

这次我们介绍的功能是Hardware Performance Monitor。

首先我们先看一下指令集 《The RISC-V Instruction Set Manual Volume II: Privileged Architecture Privileged Architecture Version 1.10》 是如何定义的。
文档链接:
https://riscv.org/specifications/privileged-isa/
在这里插入图片描述在这里插入图片描述
由上面内容可以知道,RISC-V指令集规定了一些硬件性能监控的CSR寄存器,用于监控CPU的内部运行情况。

  1. mcycle:保存自过去任意时间以来CPU执行的周期数,是一个64-bit的CSR寄存器,当使用的是32-bit系统时,可以通过mcycle & mcycleh读取完整的64-bit数据。
  2. minstret:记录自过去任意时间以来CPU退出的指令数,是一个64-bit的CSR寄存器,当使用的是32-bit系统时,可以通过minstret & minstreth读取完整的64-bit数据。
  3. mhpmevent3–mhpmevent31:硬件性能监控的事件选择CSR寄存器,是一个32-bit的CSR寄存器。
  4. mhpmcounter3– mhpmcounter31:额外29个事件触发CSR计数器,当选择监控的事件发生时,此计数器会自动加一,指令集中规定是64-bit的CSR寄存器,当使用的是32-bit系统时,可以通过mhpmcounterX & mhpmcounterXh读取完整的64-bit数据。

接下来看的是,各CSR寄存器对应的地址。
在这里插入图片描述
蓝色框中的CSR寄存器就是Hardware Performance Monitor相关的CSR寄存器。“Number”对应的12-bit数值就是该CSR寄存器对应的地址。

下面我们看看SiFive 《SiFive E31 Core Complex Manual》 关于Hardware Performance Monitor的说明。
文档链接:
https://sifive.cdn.prismic.io/sifive/0cc4918b-751d-4e2a-9337-2ceedeec4459_sifive_coreip_E31_AHB_rtl_v19_08p2p0_release_manual.pdf
在这里插入图片描述
这里看到,SiFive的E31采用的mhpmcounterX只有40-bit,而非指令集中的64-bit。不过感觉40-bit也是够用的,采用64-bit的话,会比较浪费硬件资源,所以可以理解。
黄色框的内容是说明如何使用Hardware Performance Monitor的,要理解清楚还需要配合下面这个图。
在这里插入图片描述
这个图是指导如何选择监控的事件,例如文档上的选择0x4200,则同时监控Integer load instruction retired & conditional branch instruction retired这两件事,当CPU内部发生这两件事时,计数器会自动加一。

看完文档功能说明,我们接下来看的是scala代码。
首先看的文件是:/rocket-chip/src/main/scala/rocket/Instructions.scala
从里面找出各类CSR寄存器的地址。
在这里插入图片描述
从图中可以看到,CSR寄存器的地址和指令集中的一一对应。我只是贴出部分CSR寄存器,想看全部的,大家可以自行查看Instructions.scala。

下面看一下,rocket-chip是如何实现Hardware Performance Monitor的。
主要的代码是:
/rocket-chip/src/main/scala/rocket/Events.scala
/rocket-chip/src/main/scala/rocket/RocketCore.scala
/rocket-chip/src/main/scala/rocket/CSR.scala

我只说明部分代码的功能,并不会一一解释。

首先看的是 /rocket-chip/src/main/scala/rocket/Events.scala。这个文件是一些最底层的设置。

先看EventSet这个类。

//gate是一个Bool量,比较操作
//events是一个序列,这个序列包含两个参数,第一个是字符串型,第二个是判断条件(Bool型)
class EventSet(gate: (UInt, UInt) => Bool, events: Seq[(String, () => Bool)]) {
  //方法size,作用是提取序列events的大小。
  def size = events.size
  //方法hits,作用是提取序列events中的第二个元素,即提取events序列中的判断条件。
  def hits = events.map(_._2()).asUInt
  //方法check,作用是将hits的值和mask的值进行比较,相同则输出1,不同则输出0。
  def check(mask: UInt) = gate(mask, hits)
  //方法dump,打印出events序列中各事件的名称。
  def dump() {
    for (((name, _), i) <- events.zipWithIndex)
      when (check(1.U << i)) { printf(s"Event $name\n") }
  }
  //方法withCovers。
  def withCovers {
    events.zipWithIndex.foreach {
      case ((name, func), i) => cover(gate((1.U << i), (func() << i)), name)
    }
  }
}

再看EventSets这个类。

//eventSets是EventSet类型的序列。
class EventSets(val eventSets: Seq[EventSet]) {
  //方法maskEventSelector,作用是匹配事件选择CSR寄存器的值,即mhpmeventX的值。
  def maskEventSelector(eventSel: UInt): UInt = {
  //allow full associativity between counters and event sets (for now?)
  //eventSets.size获取序列eventSets的大小,然后转换为2的幂次方数,如eventSets.size=1
  //则log2Ceil(eventSets.size)=0,因为2^0=1;若eventSets.size=2,则
  //log2Ceil(eventSets.size)=1,因为2^1=2;若eventSets.size=3,则log2Ceil(eventSets.size)=2
  //向大取,2^2=4,3和4都是2-bit的数据类型。
  //最后还有一个减1的操作,得到setMask。
  val setMask = (BigInt(1) << log2Ceil(eventSets.size)) – 1
  //eventSetIdBits在后面可以找到是8,((BigInt(1) << eventSets.map(_.size).max) - 1),得
  //到的是eventSets各序列中,最大的尺寸值,因为eventSets可能有不止一个的序列,
  //所以取最大的作为统一值,然后将此值左移8位,得到maskMask。
  val maskMask = ((BigInt(1) << eventSets.map(_.size).max) - 1) << eventSetIdBits
  //最后将eventSel的值与(setMask | maskMask).U的值进行按位与操作。
  eventSel & (setMask | maskMask).U
  }

  //方法decode,作用是拆解输入的值,拆解counter的值。
  private def decode(counter: UInt): (UInt, UInt) = {
  //require我理解大概是断言的作用,就是eventSets.size的值要小于等于8。
  require(eventSets.size <= (1 << eventSetIdBits))
  //将counter的值拆解为两部分,前一部分是counter(log2Ceil(eventSets.size)-1, 0),提取
  //counter的低位部分;后一部分是counter右移8位,提取counter的高位部分。
  (counter(log2Ceil(eventSets.size)-1, 0), counter >> eventSetIdBits)
  }
  
  //方法evaluate,作用是对输入的值(eventSel)进行搜索,有发生eventSel的事件时,返回1。
  def evaluate(eventSel: UInt): Bool = {
  //对eventSel进行拆解,拆分为set & mask。
  val (set, mask) = decode(eventSel)
  //声明sets常量,是对eventSets进行搜索,并利用mask值进行比对。
  val sets = for (e <- eventSets) yield {
  //断言,就是hits的宽度要小于等于mask的宽度值。
      require(e.hits.getWidth <= mask.getWidth, s"too many events ${e.hits.getWidth} wider than mask ${mask.getWidth}")
	  //利用check方法对mask信号进行比对。
      e check mask
      }
  //对decode出来的set信号进行sets处理,就是将set信号运行上面sets的操作。
  sets(set)
  }
  
  //调用方法withCovers。
  def cover() = eventSets.foreach { _ withCovers }

  //定义eventSetIdBits = 8
  private def eventSetIdBits = 8
}

再看 /rocket-chip/src/main/scala/rocket/CSR.scala,CSR.scala我们主要看的是maskEventSelector方法的调用。
方法maskEventSelector对应的scala代码&RTL代码位置:
在这里插入图片描述
可以看到调用maskEventSelector方法的是CSR.scala,而maskEventSelector方法则在Events.scala中进行定义的。然后我们再看看RTL的位置。
在这里插入图片描述
也可以看到是存在于CSR模块(左边的红色箭头)中。其实看RTL的后缀能看到就是出自Event.scala(右边的红色长箭头)。蓝色箭头指的就是这个maskEventSelector方法的RTL代码。配合RTL来理解scala的描述会更清晰。

最后看的是 /rocket-chip/src/main/scala/rocket/RocketCore.scala。我们只看两个地方。

第一个地方是:

csr.io.counters foreach { c => c.inc := RegNext(perfEvents.evaluate(c.eventSel)) }

这条代码的意思是,对eventSel进行evaluate的处理,eventSel信号会从CSR模块中给出,当匹配符合时,则证明选择的事件发生了,然后inc信号在下一时钟周期会等于1,否则下一时钟inc为0,inc的值会反馈至CSR模块中,同时会输给mhpmcounterX。

第二个地方是:

val perfEvents = new EventSets(Seq(
    new EventSet((mask, hits) => Mux(wb_xcpt, mask(0), wb_valid && pipelineIDToWB((mask & hits).orR)), Seq(
      ("exception", () => false.B),
      ("load", () => id_ctrl.mem && id_ctrl.mem_cmd === M_XRD && !id_ctrl.fp),
      ("store", () => id_ctrl.mem && id_ctrl.mem_cmd === M_XWR && !id_ctrl.fp),
      ("amo", () => Bool(usingAtomics) && id_ctrl.mem && (isAMO(id_ctrl.mem_cmd) || id_ctrl.mem_cmd.isOneOf(M_XLR, M_XSC))),
      ("system", () => id_ctrl.csr =/= CSR.N),
      ("arith", () => id_ctrl.wxd && !(id_ctrl.jal || id_ctrl.jalr || id_ctrl.mem || id_ctrl.fp || id_ctrl.mul || id_ctrl.div || id_ctrl.csr =/= CSR.N)),
      ("branch", () => id_ctrl.branch),
      ("jal", () => id_ctrl.jal),
      ("jalr", () => id_ctrl.jalr))
      ++ (if (!usingMulDiv) Seq() else Seq(
        ("mul", () => if (pipelinedMul) id_ctrl.mul else id_ctrl.div && (id_ctrl.alu_fn & ALU.FN_DIV) =/= ALU.FN_DIV),
        ("div", () => if (pipelinedMul) id_ctrl.div else id_ctrl.div && (id_ctrl.alu_fn & ALU.FN_DIV) === ALU.FN_DIV)))
      ++ (if (!usingFPU) Seq() else Seq(
        ("fp load", () => id_ctrl.fp && io.fpu.dec.ldst && io.fpu.dec.wen),
        ("fp store", () => id_ctrl.fp && io.fpu.dec.ldst && !io.fpu.dec.wen),
        ("fp add", () => id_ctrl.fp && io.fpu.dec.fma && io.fpu.dec.swap23),
        ("fp mul", () => id_ctrl.fp && io.fpu.dec.fma && !io.fpu.dec.swap23 && !io.fpu.dec.ren3),
        ("fp mul-add", () => id_ctrl.fp && io.fpu.dec.fma && io.fpu.dec.ren3),
        ("fp div/sqrt", () => id_ctrl.fp && (io.fpu.dec.div || io.fpu.dec.sqrt)),
        ("fp other", () => id_ctrl.fp && !(io.fpu.dec.ldst || io.fpu.dec.fma || io.fpu.dec.div || io.fpu.dec.sqrt))))),
    new EventSet((mask, hits) => (mask & hits).orR, Seq(
      ("load-use interlock", () => id_ex_hazard && ex_ctrl.mem || id_mem_hazard && mem_ctrl.mem || id_wb_hazard && wb_ctrl.mem),
      ("long-latency interlock", () => id_sboard_hazard),
      ("csr interlock", () => id_ex_hazard && ex_ctrl.csr =/= CSR.N || id_mem_hazard && mem_ctrl.csr =/= CSR.N || id_wb_hazard && wb_ctrl.csr =/= CSR.N),
      ("I$ blocked", () => icache_blocked),
      ("D$ blocked", () => id_ctrl.mem && dcache_blocked),
      ("branch misprediction", () => take_pc_mem && mem_direction_misprediction),
      ("control-flow target misprediction", () => take_pc_mem && mem_misprediction && mem_cfi && !mem_direction_misprediction && !icache_blocked),
      ("flush", () => wb_reg_flush_pipe),
      ("replay", () => replay_wb))
      ++ (if (!usingMulDiv) Seq() else Seq(
        ("mul/div interlock", () => id_ex_hazard && (ex_ctrl.mul || ex_ctrl.div) || id_mem_hazard && (mem_ctrl.mul || mem_ctrl.div) || id_wb_hazard && wb_ctrl.div)))
      ++ (if (!usingFPU) Seq() else Seq(
        ("fp interlock", () => id_ex_hazard && ex_ctrl.fp || id_mem_hazard && mem_ctrl.fp || id_wb_hazard && wb_ctrl.fp || id_ctrl.fp && id_stall_fpu)))),
    new EventSet((mask, hits) => (mask & hits).orR, Seq(
      ("I$ miss", () => io.imem.perf.acquire),
      ("D$ miss", () => io.dmem.perf.acquire),
      ("D$ release", () => io.dmem.perf.release),
      ("ITLB miss", () => io.imem.perf.tlbMiss),
      ("DTLB miss", () => io.dmem.perf.tlbMiss),
      ("L2 TLB miss", () => io.ptw.perf.l2miss)))))

这段代码定义了常量perfEvents,使用EventSets的类。实质上是以以下格式来完成的:

val perfEvents = new EventSets(
  Seq(
      new EventSet(X,Seq((事件A0名字,事件触发条件), (事件A1名字,事件触发条件)……)),
      new EventSet(X,Seq((事件B0名字,事件触发条件), (事件B1名字,事件触发条件)……)),
      new EventSet(X,Seq((事件C0名字,事件触发条件), (事件C1名字,事件触发条件)……)),
     )
)

也就是EventSets.size=3,有三个事件集序列,每个事件集里面又分事件A0、A1……。最后组成一个总的事件总集EventSets。这段代码的实际用途就是定义想监控的硬件事件,和该事件触发的条件。列举这段代码的目的是想和E31的监控事件作一个对比。
在这里插入图片描述
在这里插入图片描述
由上面两图可知,SiFive的E31和rocket-chip的硬件监控内容基本是一致。

最后阶段我们将通过实例来感受硬件性能监控功能的实际效果。
先贴出我们的测试程序。CSR寄存器的地址都在encoding.h中定义了。

#include "encoding.h"

#define U32         *(volatile unsigned int *)
#define NOP         asm volatile ("nop")

//--------------------------------------------------------------------------
// handle_trap function

void handle_trap()
{
    asm volatile ("nop");
    while(1);
}

//--------------------------------------------------------------------------
// perfevent function

void perfevent()
{
    int i;

    U32(0x60000000) = read_csr(mcycle);
    U32(0x60000004) = read_csr(mcycleh);

    U32(0x60000008) = read_csr(minstret);
    U32(0x6000000C) = read_csr(minstreth);

    U32(0x60000010) = read_csr(mhpmcounter3);
    U32(0x60000014) = read_csr(mhpmcounter3h);

    U32(0x60000018) = read_csr(mhpmcounter4);
    U32(0x6000001C) = read_csr(mhpmcounter4h);

    U32(0x60000020) = read_csr(mhpmevent3);
    U32(0x60000024) = read_csr(mhpmevent4);

    for ( i = 0; i < 1000; i++ ) NOP;

    write_csr(mcycle,       0x0);
    write_csr(mcycleh,      0x0);

    write_csr(minstret,     0x0);
    write_csr(minstreth,    0x0);

    write_csr(mhpmcounter3, 0x0);
    write_csr(mhpmcounter3h,0x0);

    write_csr(mhpmcounter4, 0x0);
    write_csr(mhpmcounter4h,0x0);

    //Data cache
    write_csr(mhpmevent3,  0x1001);
    //Memory-mapped I/O access
    write_csr(mhpmevent4,  0x0202);

    for ( i = 0; i < 1000; i++ ) U32(0x80001000+4*i) = i;

    for ( i = 0; i < 1000; i++ ) U32(0x60001000+4*i) = i;

    U32(0x60000028) = read_csr(mhpmcounter3);
    U32(0x6000002C) = read_csr(mhpmcounter3h);

    U32(0x60000030) = read_csr(mhpmcounter4);
    U32(0x60000034) = read_csr(mhpmcounter4h);
}

//--------------------------------------------------------------------------
// Main

void main()
{
    perfevent();
}

具体的编译过程可以参照下面的博客:
https://blog.csdn.net/a_weiming/article/details/105304887
https://blog.csdn.net/a_weiming/article/details/89006615
https://blog.csdn.net/a_weiming/article/details/86585963

具体的波形如下:
在这里插入图片描述
在这里插入图片描述
关于Hardware Performance Monitor的说明就到这了。

上述文章如有侵权行为,请联系我,我会立即删改,谢谢。

  • 4
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值