Files
TBgen_App/utils/verilator_call.py
2026-03-30 16:46:48 +08:00

406 lines
15 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# """
# Description : Verilator wrapper for CGA (Coverage-Guided Agent)
# Author : CorrectBench Integration
# """
# import os
# import sys
# # === [Path Auto-Configuration] ===
# # 获取当前脚本的目录
# script_dir = os.path.dirname(os.path.abspath(__file__))
# # 智能判断项目根目录:
# # 如果当前目录下有 loader_saver.py说明我们就在根目录
# if os.path.exists(os.path.join(script_dir, "loader_saver.py")):
# project_root = script_dir
# in_utils_folder = False
# else:
# # 否则假设我们在 utils/ 子目录下,根目录在上级
# project_root = os.path.dirname(script_dir)
# in_utils_folder = True
# # 1. 确保项目根目录在 sys.path 中 (以便能 import utils, config 等)
# if project_root not in sys.path:
# sys.path.insert(0, project_root)
# # 2. 只有当我们确实在 utils/ 子目录下运行时,才需要移除 script_dir
# # 这样可以避免 "import utils" 错误地导入了当前目录而不是 utils 包
# if in_utils_folder and script_dir in sys.path:
# try:
# sys.path.remove(script_dir)
# except ValueError:
# pass
# # =================================
# from utils.utils import run_in_dir
# from utils.subproc import subproc_call
# from loader_saver import autologger as logger
# # 假设 Verilator 在系统 PATH 中
# VERILATOR_BIN = "verilator"
# COVERAGE_BIN = "verilator_coverage"
# def verilator_run_coverage(run_dir, dut_file="DUT.v", tb_file="driver.v", top_module="top_module", timeout=120):
# """
# 运行 Verilator 仿真流程:编译 -> 运行 -> 生成覆盖率 -> 标注
# 返回: [success, coverage_score, annotated_file_path]
# """
# # 1. 编译
# # cmd_compile = f"{VERILATOR_BIN} --binary -j 0 --coverage {dut_file} {tb_file} --top-module {top_module}"
# cmd_compile = f"{VERILATOR_BIN} --binary -j 0 --coverage -Wno-TIMESCALEMOD -Wno-fatal {dut_file} {tb_file} --top-module {top_module}"
# # 2. 运行
# cmd_run = f"./obj_dir/V{top_module}"
# # 3. 标注
# cmd_annotate = f"{COVERAGE_BIN} --annotate logs/annotated logs/coverage.dat"
# with run_in_dir(run_dir):
# # Step 1: Compile
# res = subproc_call(cmd_compile, timeout)
# if res["haserror"]:
# logger.error(f"Verilator Compile Failed: {res['err']}")
# return False, 0.0, None
# # Step 2: Run
# res = subproc_call(cmd_run, timeout)
# if res["haserror"]:
# logger.warning(f"Verilator Run Output: {res['err']}")
# # Step 3: Annotate
# if not os.path.exists("logs"):
# os.makedirs("logs")
# res = subproc_call(cmd_annotate, timeout)
# if res["haserror"]:
# logger.error(f"Verilator Annotation Failed: {res['err']}")
# return False, 0.0, None
# # Step 4: Find Annotated File & Calculate Score
# annotated_dir = os.path.join(run_dir, "logs", "annotated")
# target_file = None
# if os.path.exists(annotated_dir):
# for f in os.listdir(annotated_dir):
# # 排除 driver/testbench只找 DUT
# if (os.path.basename(dut_file) in f or "DUT" in f) and "driver" not in f and "tb" not in f.lower():
# target_file = os.path.join(annotated_dir, f)
# break
# if not target_file:
# logger.error(f"Could not find annotated DUT file in {annotated_dir}")
# return False, 0.0, None
# score = _quick_calc_score(target_file)
# return True, score, target_file
# def _quick_calc_score(filepath):
# try:
# with open(filepath, 'r') as f:
# lines = f.readlines()
# total = 0
# covered = 0
# for line in lines:
# if line.startswith('%') or line.strip().startswith('#'):
# total += 1
# if not (line.startswith('%000000') or line.strip().startswith('#')):
# covered += 1
# return (covered / total * 100.0) if total > 0 else 0.0
# except Exception:
# return 0.0
# if __name__ == "__main__":
# print("--- Self-Test Mode: Initializing CorrectBench Environment ---")
# try:
# from config import Config
# from loader_saver import AutoLogger
# # 初始化配置,优先寻找 custom.yaml
# custom_cfg_path = os.path.join(project_root, "config/custom.yaml")
# if os.path.exists(custom_cfg_path):
# Config(custom_cfg_path)
# else:
# Config() # 使用默认配置
# # 启动日志
# AutoLogger()
# print("--- Environment Initialized. Starting Verilator Test ---")
# except Exception as e:
# print(f"Environment Init Failed: {e}")
# # 如果不是在 CorrectBench 环境下,可能无法继续
# sys.exit(1)
# # === 开始测试 ===
# if len(sys.argv) < 3:
# print("Usage: python3 verilator_call.py <run_dir> <dut_file> <tb_file>")
# print("Example: python3 verilator_call.py saves/lemmings4 prob_lemmings4.v final_TB.v")
# else:
# run_dir = sys.argv[1]
# dut = sys.argv[2]
# tb = sys.argv[3]
# success, score, path = verilator_run_coverage(run_dir, dut, tb)
# print(f"\n[Test Result]\nSuccess: {success}\nScore: {score:.2f}%\nAnnotated File: {path}")
"""
Description : Verilator wrapper for CGA - AUTO TOP-MODULE DETECTION
Author : CorrectBench Integration
"""
import os
import sys
import shutil
import re # 引入正则
# === [Path Auto-Configuration] ===
script_dir = os.path.dirname(os.path.abspath(__file__))
if os.path.exists(os.path.join(script_dir, "loader_saver.py")):
project_root = script_dir
in_utils_folder = False
else:
project_root = os.path.dirname(script_dir)
in_utils_folder = True
if project_root not in sys.path:
sys.path.insert(0, project_root)
if in_utils_folder and script_dir in sys.path:
try:
sys.path.remove(script_dir)
except ValueError:
pass
# =================================
from utils.utils import run_in_dir
from utils.subproc import subproc_call
from loader_saver import autologger as logger
VERILATOR_BIN = "verilator"
COVERAGE_BIN = "verilator_coverage"
def get_module_name(file_path):
"""
从 Verilog 文件中解析 module name
"""
if not os.path.exists(file_path):
return None
try:
with open(file_path, 'r') as f:
content = f.read()
# 匹配: module 名字 (忽略前面的空白,甚至可能有参数)
# 简单匹配 module xxx; 或 module xxx (
match = re.search(r'^\s*module\s+(\w+)', content, re.MULTILINE)
if match:
return match.group(1)
except Exception:
pass
return None
def verilator_run_coverage(run_dir, dut_file="DUT.v", tb_file="driver.v", top_module="top_module", timeout=120):
abs_run_dir = os.path.abspath(run_dir)
abs_dut = os.path.abspath(os.path.join(run_dir, dut_file))
abs_tb = os.path.abspath(os.path.join(run_dir, tb_file))
abs_obj_dir = os.path.join(abs_run_dir, "obj_dir")
abs_annotated = os.path.join(abs_run_dir, "logs", "annotated")
# 如果 tb_file 存在,优先从 tb_file 里找模块名,而不是用默认的 "top_module"
# 因为想仿真 Testbench而不是裸的 DUT
detected_top = get_module_name(abs_tb)
if detected_top:
print(f"[DEBUG] Auto-detected top module from {os.path.basename(tb_file)}: '{detected_top}'")
real_top_module = detected_top
else:
print(f"[DEBUG] Could not detect top module, using default: '{top_module}'")
real_top_module = top_module
# 清理旧编译
if os.path.exists(abs_obj_dir):
shutil.rmtree(abs_obj_dir)
# 构建编译命令
# 注意:这里只通过 --top-module 指定顶层
cmd_compile = (
f"{VERILATOR_BIN} --binary -j 0 --coverage --timing "
f"-Wno-TIMESCALEMOD -Wno-fatal -Wno-STMTDLY "
f"--Mdir {abs_obj_dir} "
f"{abs_dut} {abs_tb} --top-module {real_top_module}"
)
cmd_run = f"{abs_obj_dir}/V{real_top_module}"
cmd_annotate = f"{COVERAGE_BIN} --annotate {abs_annotated} coverage.dat"
with run_in_dir(abs_run_dir):
# Step 1: Compile
if os.path.exists("coverage.dat"): os.remove("coverage.dat")
# print(f"[DEBUG] Compiling...")
res = subproc_call(cmd_compile, timeout)
if not os.path.exists(f"{abs_obj_dir}/V{real_top_module}"):
logger.error(f"Verilator Compile Failed.")
if res['err']: print(f"[COMPILE STDERR]:\n{res['err']}")
return False, 0.0, None
# Step 2: Run
# print(f"[DEBUG] Running Simulation...")
res = subproc_call(cmd_run, timeout)
# 打印输出,确认时间是否走动
print(f"--- Simulation Output ({real_top_module}) ---")
if res['out']: print(res['out'])
# if res['err']: print(res['err']) # Warnings
if not os.path.exists("coverage.dat"):
logger.error("coverage.dat not created.")
return False, 0.0, None
# Step 3: Annotate
if not os.path.exists(abs_annotated):
os.makedirs(abs_annotated)
res = subproc_call(cmd_annotate, timeout)
# Step 4: Find Annotated File (Target: DUT)
target_file = None
generated_files = os.listdir(abs_annotated) if os.path.exists(abs_annotated) else []
if generated_files:
for f in generated_files:
# 我们的目标是看 DUT 的覆盖率
# 排除 TB 文件
is_dut = (os.path.basename(dut_file) in f) or \
(top_module in f) or \
("DUT" in f)
is_tb = ("driver" in f) or \
("tb" in f.lower() and "tb" not in os.path.basename(dut_file).lower())
# 如果自动侦测的顶层名出现在文件名里(例如 testbench.v也要排除
if real_top_module in f:
is_tb = True
if is_dut and not is_tb:
target_file = os.path.join(abs_annotated, f)
break
if not target_file:
logger.error(f"Could not find annotated DUT file in {generated_files}")
return False, 0.0, None
score = _quick_calc_score(target_file)
return True, score, target_file
# def _quick_calc_score(filepath):
# try:
# with open(filepath, 'r') as f:
# lines = f.readlines()
# total = 0
# covered = 0
# for line in lines:
# if line.startswith('%') or line.strip().startswith('#'):
# total += 1
# if not (line.startswith('%000000') or line.strip().startswith('#')):
# covered += 1
# return (covered / total * 100.0) if total > 0 else 0.0
# except Exception:
# return 0.0
def _quick_calc_score(filepath):
"""
计算 Verilator 覆盖率文件的覆盖率分数
支持的格式:
- %NNNNNN: 行覆盖计数(%000000 表示未执行)
- ~NNNNNN: 分支/条件覆盖计数
- NNNNNN: 空格开头+数字(某些 Verilator 版本)
- ^NNNNNN: 未覆盖分支标记
"""
import re
try:
with open(filepath, 'r', encoding='utf-8', errors='ignore') as f:
lines = f.readlines()
# 匹配各种覆盖率标记格式
pct_pattern = re.compile(r'^%(\d+)\s+') # %NNNNNN code
tilde_pattern = re.compile(r'^~(\d+)\s+') # ~NNNNNN code
caret_pattern = re.compile(r'^\^(\d+)\s+') # ^NNNNNN code
plain_pattern = re.compile(r'^\s*(\d+)\s+') # "NNNNNN" or " NNNNNN"
# 过滤声明语句(不计入覆盖率)
decl_pattern = re.compile(r'^\s*(input|output|inout|wire|reg|logic|parameter|localparam|assign)\b')
total = 0
covered = 0
for line in lines:
line_stripped = line.strip()
if not line_stripped:
continue
count = -1
is_covered = False
# 尝试匹配各种格式
match_pct = pct_pattern.match(line_stripped)
match_tilde = tilde_pattern.match(line_stripped)
match_caret = caret_pattern.match(line_stripped)
match_plain = plain_pattern.match(line_stripped)
if match_pct:
count = int(match_pct.group(1))
# 获取代码部分用于过滤
code_part = line_stripped[7:].strip() if len(line_stripped) > 7 else ""
if not decl_pattern.match(code_part):
total += 1
if count > 0:
covered += 1
elif match_tilde:
count = int(match_tilde.group(1))
code_part = line_stripped[7:].strip() if len(line_stripped) > 7 else ""
if not decl_pattern.match(code_part):
total += 1
if count > 0:
covered += 1
elif match_caret:
# ^ 表示未覆盖分支
code_part = line_stripped[7:].strip() if len(line_stripped) > 7 else ""
if not decl_pattern.match(code_part):
total += 1
# caret 表示未覆盖,不计入 covered
elif match_plain:
count = int(match_plain.group(1))
# 计算数字部分的长度
num_str = match_plain.group(1)
code_part = line_stripped[len(num_str):].strip()
if not decl_pattern.match(code_part):
total += 1
if count > 0:
covered += 1
return (covered / total * 100.0) if total > 0 else 0.0
except Exception as e:
print(f"[DEBUG] _quick_calc_score error: {e}")
return 0.0
if __name__ == "__main__":
try:
from config import Config
from loader_saver import AutoLogger
custom_cfg_path = os.path.join(project_root, "config/custom.yaml")
if os.path.exists(custom_cfg_path): Config(custom_cfg_path)
else: Config()
AutoLogger()
except Exception: sys.exit(1)
if len(sys.argv) < 3:
print("Usage: python3 verilator_call.py <run_dir> <dut_file> <tb_file>")
else:
run_dir = sys.argv[1]
dut = sys.argv[2]
tb = sys.argv[3]
success, score, path = verilator_run_coverage(run_dir, dut, tb)
print(f"\n[Test Result]\nSuccess: {success}\nScore: {score:.2f}%\nAnnotated File: {path}")