33#
44# This source code is licensed under the MIT license found in the
55# LICENSE file in the root directory of this source tree.
6- # (c) Meta Platforms, Inc. and affiliates. Confidential and proprietary.
76
87"""
98ARM Neoverse V3 performance report generator.
@@ -165,11 +164,11 @@ def _align(grouped_df, ev_a, ev_b):
165164
166165
167166def _sum_cmn_event (grouped_df , event_suffix ):
168- """Sum a CMN HN-S event across all chiplets (arm_cmn_0, arm_cmn_1, ...).
167+ """Sum a CMN HN-S event across all mesh instances (arm_cmn_0, arm_cmn_1, ...).
169168
170- CMN-Cypress exposes one arm_cmn_N PMU per chiplet. This helper aggregates
171- a given event across all discovered chiplets so metrics reflect the full
172- system-level cache.
169+ Arm CMN exposes one arm_cmn_N PMU per mesh instance (one per die on
170+ multi-die parts). This helper aggregates a given event across all
171+ discovered mesh instances so metrics reflect the full system-level cache.
173172 """
174173 total = None
175174 for name , group in grouped_df :
@@ -185,6 +184,32 @@ def _sum_cmn_event(grouped_df, event_suffix):
185184 return total .reset_index (drop = True )
186185
187186
187+ def _sum_cspmu_config0 (grouped_df ):
188+ """Sum the DMC memory-controller PMU data-beat counter (config=0) across all
189+ active arm_cspmu_mc_<N> channels.
190+
191+ Each arm_cspmu_mc_<N> instance is one DDR data (sub)channel; config=0 counts
192+ DDR data beats (32 B each). Summing the active channels gives the physical
193+ DRAM bandwidth (Arm "DMC Bandwidth Measurement", Phoenix SoC spec Table 15-6).
194+ """
195+ total = None
196+ for name , group in grouped_df :
197+ if (
198+ isinstance (name , str )
199+ and name .startswith ("arm_cspmu_mc_" )
200+ and "config=0" in name
201+ ):
202+ if total is None :
203+ total = group .counter_value .copy ()
204+ else :
205+ vals = group .counter_value
206+ vals .index = total .index
207+ total = total + vals
208+ if total is None :
209+ raise KeyError ("arm_cspmu_mc/config=0" )
210+ return total .reset_index (drop = True )
211+
212+
188213# ===========================================================================
189214# Core throughput metrics
190215# ===========================================================================
@@ -788,28 +813,204 @@ def dispatch_stall_mcq(grouped_df):
788813
789814
790815# ===========================================================================
791- # SVE predication effectiveness (V3-specific)
816+ # CMN mesh (uncore) + DMC memory-controller metrics
817+ #
818+ # Doc basis (Arm):
819+ # * Phoenix SoC Architecture Spec (110009_0100), "System Telemetry":
820+ # - Table 15-6 (DMS): true memory bandwidth is the DDR memory-controller
821+ # "DMC Bandwidth Measurement". On this platform the DMC PMU is exposed as
822+ # arm_cspmu_mc_<N> (one per DDR data sub-channel); config=0 counts DDR
823+ # data beats of 32 B each.
824+ # - Table 15-4 (CMN): HN-S "effectiveness" group (slc/sf hit, pocq
825+ # occupancy, mc_requests, mc_retry).
826+ # * CMN-S3 "Cyprus" mesh (arm_cmn identifier 0x43e = PART_CMN_S3; the Phoenix
827+ # SoC spec names it "CMN-Cyprus Coherent Mesh Network"). HN-S PMU event
828+ # 0x0D = PMU_HN_MC_REQS_EVENT ("requests sent to MC"); filter pmu_sn_home_sel
829+ # [40:39]: 00=All, 01=SN-bound, 10=Home-bound. (Event encoding is shared with
830+ # the CMN-700 TRM, doc 102308, cmn_hns_pmu_event_sel.)
831+ #
832+ # PRIMARY memory bandwidth is taken from the DMC PMU (arm_cspmu_mc, config=0
833+ # x 32 B) -- Arm's documented true-DRAM-bandwidth path, bounded by the physical
834+ # DDR ceiling and validated to ~2% of mm-mem.
835+ #
836+ # The CMN HN-S mc_reqs counters are used ONLY as a bounded mesh MC-request
837+ # estimate + local/remote locality split, via the SN filter
838+ # (hns_mc_reqs_{local,remote}_sn) x 64 B. "_sn" counts requests dispatched to
839+ # the memory controller (Slave Node) = actual DRAM accesses. We deliberately do
840+ # NOT use hns_mc_reqs_*_all x 64 B for bandwidth: "_all" = "_home" + "_sn"
841+ # (arrival- plus dispatch-side counts of overlapping requests), which over-reads
842+ # past the physical DRAM ceiling under saturated cross-mesh streaming.
792843# ===========================================================================
793844
794845
795846@skip_if_missing
796- def cmn_mem_read_bw_MBps (grouped_df ):
797- """Memory read bandwidth from CMN MC request counters.
847+ def dmc_mem_bw_MBps (grouped_df ):
848+ """PRIMARY memory bandwidth (read+write) from the DMC memory-controller PMU.
849+
850+ Arm's documented "DMC Bandwidth Measurement" (Phoenix SoC spec Table 15-6).
851+ Sums arm_cspmu_mc/config=0 (DDR data beats, 32 B each) over the active data
852+ channels. Bounded by the physical DDR ceiling; the ground-truth bandwidth.
853+ """
854+ beats = _sum_cspmu_config0 (grouped_df )
855+ dur = get_duration_series (grouped_df .get_group ("instructions" ))
856+ beats .index = dur .index
857+ bw_series = (beats * 32 ).div (dur )
858+ return {
859+ "name" : "DMC Memory Bandwidth (MBps)" ,
860+ "series" : bw_series ,
861+ "prefix" : 10 ** - 6 ,
862+ }
863+
864+
865+ @skip_if_missing
866+ def cmn_mem_bw_MBps (grouped_df ):
867+ """Mesh MC-request bandwidth ESTIMATE (read+write) from CMN HN-S counters.
798868
799- Each hns_mc_reqs_local_all is a cache-line (64B) request to the memory
800- controller, analogous to Grace's SCF cmem_rd_data.
869+ Sums local + remote HN-S -> memory-controller requests (SN filter) across
870+ all mesh instances; each is a 64 B cache-line request dispatched to a memory
871+ controller. This is a bounded ESTIMATE (it undercounts pure-write ~24% due to
872+ write-combining at the MC and is not a physical data-beat count) -- the DMC
873+ PMU metric above is the authoritative bandwidth. Reported for cross-check and
874+ because it provides the local/remote locality split the DMC PMU cannot.
801875 """
802- mc_reqs = _sum_cmn_event (grouped_df , "hns_mc_reqs_local_all" )
876+ mc_reqs = _sum_cmn_event (grouped_df , "hns_mc_reqs_local_sn" )
877+ try :
878+ mc_reqs = mc_reqs + _sum_cmn_event (grouped_df , "hns_mc_reqs_remote_sn" )
879+ except KeyError :
880+ pass
803881 dur = get_duration_series (grouped_df .get_group ("instructions" ))
804882 mc_reqs .index = dur .index
805883 bw_series = (mc_reqs * 64 ).div (dur )
806884 return {
807- "name" : "CMN Memory Read Bandwidth (MBps)" ,
885+ "name" : "CMN MC-Req Bandwidth (MBps, est )" ,
808886 "series" : bw_series ,
809887 "prefix" : 10 ** - 6 ,
810888 }
811889
812890
891+ @skip_if_missing
892+ def cmn_mem_local_bw_MBps (grouped_df ):
893+ """Local mesh MC-request bandwidth (est) — SN-filter requests homed on the
894+ local mesh instance's memory controllers."""
895+ mc_reqs = _sum_cmn_event (grouped_df , "hns_mc_reqs_local_sn" )
896+ dur = get_duration_series (grouped_df .get_group ("instructions" ))
897+ mc_reqs .index = dur .index
898+ bw_series = (mc_reqs * 64 ).div (dur )
899+ return {
900+ "name" : "CMN Local MC-Req Bandwidth (MBps, est)" ,
901+ "series" : bw_series ,
902+ "prefix" : 10 ** - 6 ,
903+ }
904+
905+
906+ @skip_if_missing
907+ def cmn_mem_remote_bw_MBps (grouped_df ):
908+ """Remote mesh MC-request bandwidth (est) — SN-filter requests routed to
909+ another mesh instance's memory controllers (cross-mesh / cross-node)."""
910+ mc_reqs = _sum_cmn_event (grouped_df , "hns_mc_reqs_remote_sn" )
911+ dur = get_duration_series (grouped_df .get_group ("instructions" ))
912+ mc_reqs .index = dur .index
913+ bw_series = (mc_reqs * 64 ).div (dur )
914+ return {
915+ "name" : "CMN Remote MC-Req Bandwidth (MBps, est)" ,
916+ "series" : bw_series ,
917+ "prefix" : 10 ** - 6 ,
918+ }
919+
920+
921+ @skip_if_missing
922+ def cmn_mem_read_pct (grouped_df ):
923+ """Approximate read share of memory traffic from HN-S PoCQ occupancy.
924+
925+ The HN-S has no read/write split on MC requests, but the QoS PoCQ
926+ occupancy counters (read vs write) track how long read- vs write-class
927+ requests sit in the point-of-coherency queue, giving a usable read/write
928+ mix proxy. Reported as the read fraction of (read + write) occupancy.
929+ """
930+ read_occ = _sum_cmn_event (grouped_df , "hns_qos_pocq_occupancy_read" )
931+ write_occ = _sum_cmn_event (grouped_df , "hns_qos_pocq_occupancy_write" )
932+ write_occ .index = read_occ .index
933+ total = read_occ + write_occ
934+ return {
935+ "name" : "CMN Memory Read Mix %" ,
936+ "series" : read_occ .div (total ),
937+ "prefix" : 100 ,
938+ }
939+
940+
941+ @skip_if_missing
942+ def cmn_mc_retry_pct (grouped_df ):
943+ """Memory-controller retry rate — retried MC requests / total MC requests.
944+
945+ Maps to the HN-S "mc_retry" metric. A high retry rate indicates
946+ the memory controller is backpressuring the mesh (DRAM-bound).
947+ """
948+ retries = _sum_cmn_event (grouped_df , "hns_mc_retries_local_all" )
949+ try :
950+ retries = retries + _sum_cmn_event (grouped_df , "hns_mc_retries_remote_all" )
951+ except KeyError :
952+ pass
953+ reqs = _sum_cmn_event (grouped_df , "hns_mc_reqs_local_all" )
954+ try :
955+ reqs = reqs + _sum_cmn_event (grouped_df , "hns_mc_reqs_remote_all" )
956+ except KeyError :
957+ pass
958+ retries .index = reqs .index
959+ return {
960+ "name" : "CMN MC Retry %" ,
961+ "series" : retries .div (reqs ),
962+ "prefix" : 100 ,
963+ }
964+
965+
966+ @skip_if_missing
967+ def cmn_sf_hit_rate (grouped_df ):
968+ """Snoop-filter hit rate — hns_sf_hit_all / hns_slc_sf_cache_access_all.
969+
970+ Maps to the HN-S "sf_hit_ratio". Complements the existing SLC
971+ (L3) hit-rate metric with coherence-directory effectiveness.
972+ """
973+ hit_s = _sum_cmn_event (grouped_df , "hns_sf_hit_all" )
974+ access_s = _sum_cmn_event (grouped_df , "hns_slc_sf_cache_access_all" )
975+ hit_s .index = access_s .index
976+ return {
977+ "name" : "CMN Snoop Filter Hit Rate %" ,
978+ "series" : hit_s .div (access_s ),
979+ "prefix" : 100 ,
980+ }
981+
982+
983+ @skip_if_missing
984+ def cmn_mesh_freq_ghz (grouped_df ):
985+ """Mesh clock frequency (GHz) derived from the DTC cycle counter.
986+
987+ dtc_cycles increments at the mesh clock; dividing by the wall-clock
988+ sample duration recovers the effective mesh frequency. Useful for
989+ detecting mesh DVFS throttling under load.
990+ """
991+ cyc = _sum_cmn_event (grouped_df , "dtc_cycles" )
992+ # dtc_cycles is summed across mesh instances; use the per-instance average.
993+ n_cmn = 0
994+ for name , _ in grouped_df :
995+ if isinstance (name , str ) and name .endswith ("/dtc_cycles/" ):
996+ n_cmn += 1
997+ dur = get_duration_series (grouped_df .get_group ("instructions" ))
998+ cyc .index = dur .index
999+ if n_cmn > 1 :
1000+ cyc = cyc / n_cmn
1001+ freq = cyc .div (dur ) # cycles per second
1002+ return {
1003+ "name" : "CMN Mesh Frequency (GHz)" ,
1004+ "series" : freq ,
1005+ "prefix" : 10 ** - 9 ,
1006+ }
1007+
1008+
1009+ # ===========================================================================
1010+ # SVE predication effectiveness (V3-specific)
1011+ # ===========================================================================
1012+
1013+
8131014@skip_if_missing
8141015def sve_pred_empty_pct (grouped_df ):
8151016 """SVE predicated ops with no active lanes (wasted work)."""
@@ -946,8 +1147,15 @@ def main(
9461147 sve_pred_empty_pct (grouped_df ),
9471148 sve_pred_full_pct (grouped_df ),
9481149 sve_pred_partial_pct (grouped_df ),
949- # --- CMN uncore (SLC / memory bandwidth) ---
950- cmn_mem_read_bw_MBps (grouped_df ),
1150+ # --- CMN mesh (uncore) metrics ---
1151+ dmc_mem_bw_MBps (grouped_df ),
1152+ cmn_mem_bw_MBps (grouped_df ),
1153+ cmn_mem_local_bw_MBps (grouped_df ),
1154+ cmn_mem_remote_bw_MBps (grouped_df ),
1155+ cmn_mem_read_pct (grouped_df ),
1156+ cmn_mc_retry_pct (grouped_df ),
1157+ cmn_sf_hit_rate (grouped_df ),
1158+ cmn_mesh_freq_ghz (grouped_df ),
9511159 ]
9521160
9531161 filtered_metrics = list (itertools .filterfalse (lambda x : x is None , metrics ))
0 commit comments