# """ # 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 ") # 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 ") 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}")