上传所有文件

This commit is contained in:
zice6688
2026-03-30 16:46:48 +08:00
parent 8c2008c738
commit 35c99bac58
110 changed files with 23243 additions and 0 deletions

406
utils/verilator_call.py Normal file
View File

@@ -0,0 +1,406 @@
# """
# 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}")