7.1-2"Zicntr" & "Zihpm": Counters and Timers

Part I Linux boot: required Vol. I (Unprivileged) pp. 51–54 · ~4 min read

  • cycle counter
  • time counter
  • instret
  • hpmcounter

RISC-V reserves thirty-two unprivileged, read-only, 64-bit counters at CSR addresses 0xC000xC1F — on RV32, their upper halves appear as separate CSRs at 0xC800xC9F. The first three (Zicntr) have fixed meanings; the other twenty-nine (Zihpm) are platform-defined performance counters. Both extensions build on Zicsr, and the “RD…” mnemonics are just pseudoinstructions for csrrs rd, counter, x0.

The unprivileged counter file
CSR (RV32 high half)CountsSharp edges
cycle0xC00 (0xC80)Core clock cycles since an arbitrary startPer-CORE, not per-hart
time0xC01 (0xC81)Wall-clock ticks since an arbitrary startCross-hart sync within 1 tick
instret0xC02 (0xC82)Instructions retired by this hartFaulting instructions don’t count
hpmcounter3–310xC03–0xC1F (+0xC83–)Platform-defined events (Zihpm)Unimplemented → trap or constant
Dotted-underlined cells have explanations — click one.

Why 64-bit even on RV32 — and how to read one safely

Anything narrower makes overflow undetectable. The cost is that an RV32 hart reads a 64-bit counter in two halves, racing the counter itself. The canonical fix (Listing 1):

again:
    rdcycleh  x3        # high
    rdcycle   x2        # low
    rdcycleh  x4        # high, again
    bne       x3, x4, again

If the two high-half reads match, the low half cannot have wrapped between them — the pair x3:x2 is a coherent 64-bit sample. No hardware snapshot latches required; the spec considered and rejected them (they’d bloat the user-visible context on richly countered machines).

Zihpm: the programmable 29

hpmcounter3hpmcounter31 exist for microarchitectural event counting — cache misses, branch mispredicts, whatever the platform defines. The unprivileged spec deliberately says almost nothing: implemented count, widths, and events are platform-specific; reading an unimplemented one may raise illegal-instruction or return a constant; a misconfigured event may read constant. The machinery that picks events and grants user access lives in the privileged architecture (Vol II ch. 3: mhpmevent*, mcounteren).

Hardware Designer Notes

For a Linux-bootable v1: three real counters, and time almost certainly sourced from the memory-mapped mtime of your platform timer (Vol II ch. 3.2) — a synchronized, single source of truth trivially satisfies the one-tick rule. The instret gate belongs in your commit stage next to the exception logic; getting ECALL counted is a classic off-by-one against reference models. hpmcounters can wait: expose none, let mcounteren hide them, and Linux’s perf falls back gracefully.

Minimal Linux-boot hart MUST

  • Make cycle/time/instret 64-bit counters; on RV32 expose high halves at 0xC80+ as separate CSRs
  • Gate the instret increment on retirement — trap-raising instructions must not count
  • Freeze cycle when the core is fully clock-gated/powered down in deep sleep
  • Keep RDTIME cross-hart skew unobservable beyond one tick and strictly monotonic

MAY simplify / trap-and-emulate

  • Implement time by distributing the platform mtime value (the usual design) rather than a per-core clock
  • Return the cycle count for RDTIME on a simple single-clock platform
  • Trap OR return a constant for unimplemented hpmcounters — document which
  • Implement zero hpm events at first: Linux boots fine with Zicntr alone

Check yourself — counters & timers

1.An ECALL executes. Does instret increment?

2.Why does the RV32 64-bit counter read loop (rdcycleh; rdcycle; rdcycleh; bne → retry) work?

3.Your platform updates the time CSR value only at 100 MHz, but the tick period is defined as 1 ns (1 GHz). Compliant?

4.User code reads hpmcounter7 on your core, which implements only hpmcounter3-6. What may happen?

4 questions