课题12:RISC-V后端指令计数统计器

难度:中 | 类型:项目实战 | 源文件:scratchv/backend/inst_counter.py | 行数:~600 状态:✅ 生产级版本已完成 | 🚀 已支持 9 大标准分类、结构化数据模型与多格式 A/B 对比

实施文档:设计文档 | 开发文档


概述

解析生成的 RISC-V 汇编,统计不同类型指令的数量(算术、浮点、访存、分支、跳转、原子、系统等),生成分类统计与多文件对比报告。


理解背景

是什么?

指令计数统计器(inst_counter.py)解析 RISC-V 汇编文件,按标准 9 大主类别统计指令数量,并输出文本表格、Markdown、JSON、HTML 和可视化图表。

统计类别:

类别 包含的指令 例子
ALU 整数算术/逻辑/移位/比较/上位立即数 add, sub, mul, div, srai, and, or, addi, lui, slt
FP 浮点算术/融合乘加/开方/转换/比较 fadd.s, fmadd.d, fsqrt.s, fcvt.w.s, fmv.x.w
MEM 整数与浮点内存读写(含压缩指令) lw, sw, lh, sh, flw, fsw, c.lw, c.sw
BRANCH 条件分支及分支伪指令 beq, bne, blt, bge, beqz, bnez, c.beqz
JUMP 无条件跳转、函数调用、返回等控制流 j, jal, jalr, ret, call, tail, c.j
ATOMIC 原子内存操作(A扩展及内存顺序后缀) lr.w, sc.w, amoadd.w, amoswap.d.aqrl
SYSTEM 内存屏障、CSR寄存器、环境调用与特权指令 fence, fence.i, csrrw, ecall, ebreak, sfence.vma
PSEUDO 非控制流通用汇编语法糖 li, la, mv, nop, not, neg
MISC 其他未收录/自定义专用扩展指令 unknown_opcodes

为什么?

编译器优化的核心指标之一就是指令数。你需要知道:

  1. 各类指令占比:ALU 多 = 计算密集,MEM 多 = 数据密集
  2. 优化前后对比:优化到底减少了多少条指令?哪一类减少最多?
  3. ScratchV vs LLVM:两个编译器的指令分布有什么不同?

代码走读

命令行使用
# 先编译 CNN 模型到汇编
python3 scratchv/standalone/onnx_to_riscv_standalone.py models/graph/cnn.onnx \
    -o /tmp/cnn_riscv.bin

# 统计汇编文件的指令分布
python3 -m scratchv.backend.inst_counter /tmp/cnn_riscv.s

实际输出(CNN 模型,887 条指令):

============================================================
RISC-V Instruction Statistics
============================================================
Category      Count    Percent Bar
------------------------------------------------------------
ALU             604      68.1% ##################################
MEM              92      10.4% #####
BRANCH           48       5.4% ##
JUMP              3       0.3% #
PSEUDO          140      15.8% #######
MISC              0       0.0% #
------------------------------------------------------------
TOTAL           887

------------------------------------------------------------
Per-instruction Breakdown
------------------------------------------------------------
  addi            171  (ALU)
  add             154  (ALU)
  li              108  (PSEUDO)
  lui              77  (ALU)
  lw               75  (MEM)
  mul              71  (ALU)
  slt              47  (ALU)
  slli             44  (ALU)
  bne              43  (BRANCH)
  srai             30  (ALU)
  sw               17  (MEM)
  mv               16  (PSEUDO)
  nop              16  (PSEUDO)
  slti              5  (ALU)
  beq               5  (BRANCH)
  and               4  (ALU)
  j                 2  (JUMP)
  auipc             1  (ALU)
  ret               1  (JUMP)

生成图表和 HTML 报告
from scratchv.backend.inst_counter import (
    count_instructions_file, generate_chart, generate_html_report
)

counts = count_instructions_file("/tmp/cnn_riscv.s")
generate_chart(counts, "/tmp/inst_chart.png")        # 需要 pip install matplotlib
generate_html_report(counts, "/tmp/inst_report.html", title="CNN Model")

多文件对比
from scratchv.backend.inst_counter import compare_files

# 对比多个汇编文件
result = compare_files(["/tmp/cnn_riscv.s", "/tmp/llvm_output.s"])
# result.counts  → {"file1": Counter(...), "file2": Counter(...)}
# result.diffs   → {"file2": {"ALU": +50, "MEM": -10, ...}}
# result.to_dataframe() → pandas DataFrame

Python API 基础用法
from scratchv.backend.inst_counter import count_instructions_file, format_table

counts = count_instructions_file("/tmp/cnn_riscv.s")
print(format_table(counts))

动手练习

练习 1: 分析 CNN 模型的指令分布

用 make bench-cnn 生成汇编,统计各类指令占比。哪类最多?为什么?

练习 2: 优化前后对比

对同一段代码,分别统计优化前和优化后的指令分布,计算减少了多少条、哪些类别变化最大。

练习 3: 添加新的指令分类

在 _OPCODE_CATEGORIES 字典中添加 RISC-V F/D 扩展(浮点)指令的映射。


常见坑
坑 说明
伪指令统计 li a0, 5 统计为 PSEUDO 类别,不会展开为真指令后再统计
matplotlib 依赖 生成图表需要 pip install matplotlib,不是必需功能
指令识别遗漏 某些特殊格式的汇编行可能无法提取操作码(如带复杂寻址模式的指令)

进阶阅读