A performance monitoring unit (PMU) counts microarchitectural events such as retired instructions, cycles, cache misses, and branch mispredicts. Fixed-function counters track a small set of common events with no setup.
Programmable counters track a selected event each, chosen from the microarchitecture event list. This topic covers method only, never vendor event maps.
These are not the counters of digital design. An RTL counter divides a clock, while a PMU counter observes behavior.
Kind | Counts | Cost of using it |
|---|---|---|
Fixed-function | Cycles, instructions, and a few common events | Near zero: always available |
Programmable | One selected event per counter | One setup step per counter per run |
Per-core | Events for one core | Must be aggregated for chip claims |
System-wide | Fabric, memory controller, or accelerator events | Needs a synchronized window with core counts |
A counter increments only when every gate agrees: the counter is enabled, the privilege filter admits the current mode, and the event fires. Any disabled gate freezes the count.
Each counter is N bits wide, so it wraps after 2^N events and raises an overflow indication. A 32-bit cycle counter on a 3 GHz core wraps every 2^32 / 3e9 seconds, about 1.4 seconds.
overflow period = 2^32 cycles / 3.0e9 cycles/s
= 4294967296 / 3000000000 s
= 1.43 s (about 1.4 s)Per-core counts cannot answer system questions alone. A core that looks idle may be waiting on a saturated memory controller that only system counters can see.
Enable, privilege filter, and event selection. The counter only moves when all three agree, so a closed gate explains a low count with normal cycles.
About every 1.4 seconds. The calc block above shows 2^32 / 3e9 = 1.43 s, so drain well inside that bound.
A small saturating PMU counter with enable, privilege gate, and sticky overflow, plus a testbench that proves each gate.
module pmc_counter #(
parameter int W = 4
)(
input logic clk,
input logic rst_n,
input logic en,
input logic priv_ok,
input logic evt,
output logic [W-1:0] count,
output logic sticky
);
logic [W-1:0] cnt_q;
logic sticky_q;
assign count = cnt_q;
assign sticky = sticky_q;
always_ff @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
cnt_q <= '0;
sticky_q <= 1'b0;
end else if (en && priv_ok && evt) begin
if (cnt_q == {W{1'b1}}) begin
cnt_q <= '0;
sticky_q <= 1'b1;
end else begin
cnt_q <= cnt_q + 1'b1;
end
end
end
endmodule