From 35c99bac5809860362ff5be96af8cd403870ae43 Mon Sep 17 00:00:00 2001 From: zice6688 <119166499+zice6688@users.noreply.github.com> Date: Mon, 30 Mar 2026 16:46:48 +0800 Subject: [PATCH] =?UTF-8?q?=E4=B8=8A=E4=BC=A0=E6=89=80=E6=9C=89=E6=96=87?= =?UTF-8?q?=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- LLM_call.py | 799 +++ __pycache__/LLM_call.cpython-312.pyc | Bin 0 -> 27614 bytes __pycache__/analyze.cpython-312.pyc | Bin 0 -> 43833 bytes __pycache__/iverilog_call.cpython-312.pyc | Bin 0 -> 6589 bytes __pycache__/loader_saver.cpython-312.pyc | Bin 0 -> 29431 bytes __pycache__/run_tbgen.cpython-312.pyc | Bin 0 -> 5639 bytes analyze.py | 1059 ++++ autoline/TB1_gen.py | 43 + autoline/TB2_syncheck.py | 416 ++ autoline/TB3_funccheck.py | 836 +++ autoline/TB4_eval.py | 245 + autoline/TB_autoline.py | 537 ++ autoline/TB_cga.py | 1453 +++++ autoline/__init__.py | 22 + autoline/__pycache__/TB1_gen.cpython-312.pyc | Bin 0 -> 2225 bytes .../__pycache__/TB2_syncheck.cpython-312.pyc | Bin 0 -> 26693 bytes .../__pycache__/TB3_funccheck.cpython-312.pyc | Bin 0 -> 59802 bytes autoline/__pycache__/TB4_eval.cpython-312.pyc | Bin 0 -> 13497 bytes .../__pycache__/TB_autoline.cpython-312.pyc | Bin 0 -> 30311 bytes autoline/__pycache__/TB_cga.cpython-312.pyc | Bin 0 -> 35891 bytes autoline/__pycache__/__init__.cpython-312.pyc | Bin 0 -> 848 bytes .../__pycache__/cga_utils.cpython-312.pyc | Bin 0 -> 96265 bytes .../diversity_injector.cpython-312.pyc | Bin 0 -> 23994 bytes .../energy_allocator.cpython-312.pyc | Bin 0 -> 32657 bytes .../quality_evaluator.cpython-312.pyc | Bin 0 -> 39790 bytes .../semantic_analyzer.cpython-312.pyc | Bin 0 -> 44148 bytes .../__pycache__/test_history.cpython-312.pyc | Bin 0 -> 25624 bytes autoline/cga_utils.py | 5444 +++++++++++++++++ autoline/diversity_injector.py | 601 ++ autoline/energy_allocator.py | 787 +++ autoline/quality_evaluator.py | 1039 ++++ autoline/semantic_analyzer.py | 1029 ++++ autoline/test_history.py | 580 ++ config/__init__.py | 8 + config/__pycache__/__init__.cpython-312.pyc | Bin 0 -> 338 bytes config/__pycache__/config.cpython-312.pyc | Bin 0 -> 13032 bytes config/config.py | 330 + config/custom.yaml | 15 + config/default.yaml | 96 + config/key_API.json | 5 + data/__init__.py | 0 data/__pycache__/__init__.cpython-312.pyc | Bin 0 -> 148 bytes data/__pycache__/probset.cpython-312.pyc | Bin 0 -> 14781 bytes data/probset.py | 268 + iverilog_call.py | 156 + loader_saver.py | 584 ++ main.py | 98 + .../example_mul/1_TBgen/TBgen_codes/TBout.txt | 30 + .../1_TBgen/TBgen_codes/example_mul.v | 3 + .../1_TBgen/TBgen_codes/example_mul_tb.py | 82 + .../1_TBgen/TBgen_codes/example_mul_tb.v | 138 + .../example_mul/1_TBgen/TBgen_codes/run.vvp | 218 + .../1_TBgen/TBgen_codes/run_info.txt | 15 + .../1_TBgen/TBgen_codes/run_info_py.txt | 35 + .../example_mul/1_TBgen/TBgen_codes/vlist.txt | 2 + .../example_mul/1_TBgen/stage_0.txt | 37 + .../example_mul/1_TBgen/stage_1.txt | 42 + .../example_mul/1_TBgen/stage_2.txt | 98 + .../example_mul/1_TBgen/stage_3.txt | 174 + .../example_mul/1_TBgen/stage_4.txt | 223 + .../example_mul/1_TBgen/stage_4b.txt | 310 + .../example_mul/1_TBgen/stage_5.txt | 135 + .../example_mul/1_TBgen/stage_checklist.txt | 2 + .../3_TBeval/eval1_GoldenRTL/TBout.txt | 30 + .../3_TBeval/eval1_GoldenRTL/example_mul.v | 10 + .../eval1_GoldenRTL/example_mul_tb.py | 82 + .../3_TBeval/eval1_GoldenRTL/example_mul_tb.v | 138 + .../3_TBeval/eval1_GoldenRTL/run.vvp | 226 + .../3_TBeval/eval1_GoldenRTL/run_info.txt | 15 + .../3_TBeval/eval1_GoldenRTL/run_info_py.txt | 24 + .../3_TBeval/eval1_GoldenRTL/vlist.txt | 2 + output/example_mul/example_mul/DUT.v | 10 + output/example_mul/example_mul/final_TB.py | 82 + output/example_mul/example_mul/final_TB.v | 138 + output/example_mul/example_mul/run_info.json | 29 + .../example_mul/run_info_short.json | 8 + output/example_mul/example_mul/task_log.log | 16 + output/example_mul_tb.v | 138 + prompt_scripts/__init__.py | 21 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 879 bytes .../__pycache__/base_script.cpython-312.pyc | Bin 0 -> 15890 bytes .../script_directgen.cpython-312.pyc | Bin 0 -> 3872 bytes .../script_pychecker.cpython-312.pyc | Bin 0 -> 23383 bytes .../script_pychecker_CMB_new.cpython-312.pyc | Bin 0 -> 17802 bytes .../script_pychecker_SEQ.cpython-312.pyc | Bin 0 -> 32877 bytes .../__pycache__/utils.cpython-312.pyc | Bin 0 -> 28704 bytes prompt_scripts/base_script.py | 319 + .../script_RTLchecker0306.cpython-312.pyc | Bin 0 -> 21956 bytes .../legacy/script_RTLchecker0306.py | 288 + prompt_scripts/public_stages.py | 10 + prompt_scripts/script_directgen.py | 60 + prompt_scripts/script_pychecker.py | 311 + prompt_scripts/script_pychecker_CMB_new.py | 313 + prompt_scripts/script_pychecker_SEQ.py | 527 ++ prompt_scripts/utils.py | 803 +++ requirements.txt | 6 + results/example_mul.json | 9 + run_tbgen.py | 209 + .../20260330_154844/20260330_154844.log | 255 + .../20260330_155017/20260330_155017.log | 204 + .../20260330_162521/20260330_162521.log | 192 + utils/__init__.py | 0 utils/__pycache__/__init__.cpython-312.pyc | Bin 0 -> 149 bytes utils/__pycache__/subproc.cpython-312.pyc | Bin 0 -> 1972 bytes utils/__pycache__/utils.cpython-312.pyc | Bin 0 -> 11808 bytes .../verilator_call.cpython-312.pyc | Bin 0 -> 10275 bytes utils/json_utils.py | 54 + utils/subproc.py | 45 + utils/utils.py | 269 + utils/verilator_call.py | 406 ++ 110 files changed, 23243 insertions(+) create mode 100644 LLM_call.py create mode 100644 __pycache__/LLM_call.cpython-312.pyc create mode 100644 __pycache__/analyze.cpython-312.pyc create mode 100644 __pycache__/iverilog_call.cpython-312.pyc create mode 100644 __pycache__/loader_saver.cpython-312.pyc create mode 100644 __pycache__/run_tbgen.cpython-312.pyc create mode 100644 analyze.py create mode 100644 autoline/TB1_gen.py create mode 100644 autoline/TB2_syncheck.py create mode 100644 autoline/TB3_funccheck.py create mode 100644 autoline/TB4_eval.py create mode 100644 autoline/TB_autoline.py create mode 100644 autoline/TB_cga.py create mode 100644 autoline/__init__.py create mode 100644 autoline/__pycache__/TB1_gen.cpython-312.pyc create mode 100644 autoline/__pycache__/TB2_syncheck.cpython-312.pyc create mode 100644 autoline/__pycache__/TB3_funccheck.cpython-312.pyc create mode 100644 autoline/__pycache__/TB4_eval.cpython-312.pyc create mode 100644 autoline/__pycache__/TB_autoline.cpython-312.pyc create mode 100644 autoline/__pycache__/TB_cga.cpython-312.pyc create mode 100644 autoline/__pycache__/__init__.cpython-312.pyc create mode 100644 autoline/__pycache__/cga_utils.cpython-312.pyc create mode 100644 autoline/__pycache__/diversity_injector.cpython-312.pyc create mode 100644 autoline/__pycache__/energy_allocator.cpython-312.pyc create mode 100644 autoline/__pycache__/quality_evaluator.cpython-312.pyc create mode 100644 autoline/__pycache__/semantic_analyzer.cpython-312.pyc create mode 100644 autoline/__pycache__/test_history.cpython-312.pyc create mode 100644 autoline/cga_utils.py create mode 100644 autoline/diversity_injector.py create mode 100644 autoline/energy_allocator.py create mode 100644 autoline/quality_evaluator.py create mode 100644 autoline/semantic_analyzer.py create mode 100644 autoline/test_history.py create mode 100644 config/__init__.py create mode 100644 config/__pycache__/__init__.cpython-312.pyc create mode 100644 config/__pycache__/config.cpython-312.pyc create mode 100644 config/config.py create mode 100644 config/custom.yaml create mode 100644 config/default.yaml create mode 100644 config/key_API.json create mode 100644 data/__init__.py create mode 100644 data/__pycache__/__init__.cpython-312.pyc create mode 100644 data/__pycache__/probset.cpython-312.pyc create mode 100644 data/probset.py create mode 100644 iverilog_call.py create mode 100644 loader_saver.py create mode 100644 main.py create mode 100644 output/example_mul/example_mul/1_TBgen/TBgen_codes/TBout.txt create mode 100644 output/example_mul/example_mul/1_TBgen/TBgen_codes/example_mul.v create mode 100644 output/example_mul/example_mul/1_TBgen/TBgen_codes/example_mul_tb.py create mode 100644 output/example_mul/example_mul/1_TBgen/TBgen_codes/example_mul_tb.v create mode 100644 output/example_mul/example_mul/1_TBgen/TBgen_codes/run.vvp create mode 100644 output/example_mul/example_mul/1_TBgen/TBgen_codes/run_info.txt create mode 100644 output/example_mul/example_mul/1_TBgen/TBgen_codes/run_info_py.txt create mode 100644 output/example_mul/example_mul/1_TBgen/TBgen_codes/vlist.txt create mode 100644 output/example_mul/example_mul/1_TBgen/stage_0.txt create mode 100644 output/example_mul/example_mul/1_TBgen/stage_1.txt create mode 100644 output/example_mul/example_mul/1_TBgen/stage_2.txt create mode 100644 output/example_mul/example_mul/1_TBgen/stage_3.txt create mode 100644 output/example_mul/example_mul/1_TBgen/stage_4.txt create mode 100644 output/example_mul/example_mul/1_TBgen/stage_4b.txt create mode 100644 output/example_mul/example_mul/1_TBgen/stage_5.txt create mode 100644 output/example_mul/example_mul/1_TBgen/stage_checklist.txt create mode 100644 output/example_mul/example_mul/3_TBeval/eval1_GoldenRTL/TBout.txt create mode 100644 output/example_mul/example_mul/3_TBeval/eval1_GoldenRTL/example_mul.v create mode 100644 output/example_mul/example_mul/3_TBeval/eval1_GoldenRTL/example_mul_tb.py create mode 100644 output/example_mul/example_mul/3_TBeval/eval1_GoldenRTL/example_mul_tb.v create mode 100644 output/example_mul/example_mul/3_TBeval/eval1_GoldenRTL/run.vvp create mode 100644 output/example_mul/example_mul/3_TBeval/eval1_GoldenRTL/run_info.txt create mode 100644 output/example_mul/example_mul/3_TBeval/eval1_GoldenRTL/run_info_py.txt create mode 100644 output/example_mul/example_mul/3_TBeval/eval1_GoldenRTL/vlist.txt create mode 100644 output/example_mul/example_mul/DUT.v create mode 100644 output/example_mul/example_mul/final_TB.py create mode 100644 output/example_mul/example_mul/final_TB.v create mode 100644 output/example_mul/example_mul/run_info.json create mode 100644 output/example_mul/example_mul/run_info_short.json create mode 100644 output/example_mul/example_mul/task_log.log create mode 100644 output/example_mul_tb.v create mode 100644 prompt_scripts/__init__.py create mode 100644 prompt_scripts/__pycache__/__init__.cpython-312.pyc create mode 100644 prompt_scripts/__pycache__/base_script.cpython-312.pyc create mode 100644 prompt_scripts/__pycache__/script_directgen.cpython-312.pyc create mode 100644 prompt_scripts/__pycache__/script_pychecker.cpython-312.pyc create mode 100644 prompt_scripts/__pycache__/script_pychecker_CMB_new.cpython-312.pyc create mode 100644 prompt_scripts/__pycache__/script_pychecker_SEQ.cpython-312.pyc create mode 100644 prompt_scripts/__pycache__/utils.cpython-312.pyc create mode 100644 prompt_scripts/base_script.py create mode 100644 prompt_scripts/legacy/__pycache__/script_RTLchecker0306.cpython-312.pyc create mode 100644 prompt_scripts/legacy/script_RTLchecker0306.py create mode 100644 prompt_scripts/public_stages.py create mode 100644 prompt_scripts/script_directgen.py create mode 100644 prompt_scripts/script_pychecker.py create mode 100644 prompt_scripts/script_pychecker_CMB_new.py create mode 100644 prompt_scripts/script_pychecker_SEQ.py create mode 100644 prompt_scripts/utils.py create mode 100644 requirements.txt create mode 100644 results/example_mul.json create mode 100644 run_tbgen.py create mode 100644 saves/0330~0405/20260330_154844/20260330_154844.log create mode 100644 saves/0330~0405/20260330_155017/20260330_155017.log create mode 100644 saves/0330~0405/20260330_162521/20260330_162521.log create mode 100644 utils/__init__.py create mode 100644 utils/__pycache__/__init__.cpython-312.pyc create mode 100644 utils/__pycache__/subproc.cpython-312.pyc create mode 100644 utils/__pycache__/utils.cpython-312.pyc create mode 100644 utils/__pycache__/verilator_call.cpython-312.pyc create mode 100644 utils/json_utils.py create mode 100644 utils/subproc.py create mode 100644 utils/utils.py create mode 100644 utils/verilator_call.py diff --git a/LLM_call.py b/LLM_call.py new file mode 100644 index 0000000..053f677 --- /dev/null +++ b/LLM_call.py @@ -0,0 +1,799 @@ +""" +Description : This file is related to GPT call, include the function of calling GPT and the function of running GPT in chatgpt mode +Author : Ruidi Qiu (ruidi.qiu@tum.de) +Time : 2023/11/17 15:01:06 +LastEdited : 2024/9/3 16:52:31 +""" +from config import Config +from openai import OpenAI +from anthropic import Anthropic +import loader_saver as ls +from utils.utils import Timer +import tiktoken +import copy +import re +import requests +import json +import httpx +import time +# import Image +import openai +import os +from datetime import datetime, timedelta, timezone +from config.config import GPT_MODELS +from http import HTTPStatus + + +__all__ = ["llm_call", "gpt_call", "claude_call", "run_like_a_chatgpt"] + +PRICING_MODELS = { + # model: [price_per_1000_prompt_tokens, price_per_1000_completion_tokens] + # qwen + "qwen-max": [0.02, 0.06], + "qwen-plus": [0.004, 0.012], + # claude + "claude-3-5-sonnet-20240620": [0.003, 0.015], + "claude-3-opus-20240229": [0.015, 0.075], + "claude-3-sonnet-20240229": [0.003, 0.015], + "claude-3-haiku-20240307": [0.00025, 0.00125], + "claude-2.1": [0.008, 0.024], + "claude-2.0": [0.008, 0.024], + # gpt 4o + 'gpt-4o-2024-08-06' : [0.0025, 0.01], + 'gpt-4o-2024-05-13' : [0.005, 0.015], + 'gpt-4o-mini-2024-07-18' : [0.00015, 0.0006], + # gpt 4 turbo + 'gpt-4-turbo-2024-04-09': [0.01, 0.03], + 'gpt-4-0125-preview': [0.01, 0.03], + 'gpt-4-1106-preview': [0.01, 0.03], + 'gpt-4-1106-vision-preview': [0.01, 0.03], + # gpt 4 (old) + 'gpt-4': [0.03, 0.06], + 'gpt-4-32k': [0.06, 0.12], + # gpt 3.5 turbo + 'gpt-3.5-turbo-0125': [0.0005, 0.0015], + 'gpt-3.5-turbo-instruct': [0.0015, 0.0020], + # gpt 3.5 turbo old + 'gpt-3.5-turbo-1106': [0.0010, 0.0020], + 'gpt-3.5-turbo-0613': [0.0015, 0.0020], + 'gpt-3.5-turbo-16k-0613': [0.0030, 0.0040], + 'gpt-3.5-turbo-0301': [0.0030, 0.0040] +} + +JSON_MODELS = ["gpt-4-0613", "gpt-4-32k-0613", "gpt-3.5-turbo-0613", "gpt-3.5-turbo-16k-0613"] + +# MODEL_REDIRECTION is in config + +# DEFAULT_SYS_MESSAGE = "You are the strongest AI in the world. I always trust you. Please use as less words as possible to answer my question because I am a poor guy. But do not save words by discarding information." +DEFAULT_SYS_MESSAGE = "You are the strongest AI in the world. I always trust you. You already have the knowledge about python and verilog. Do not save words by discarding information." +RUN_LIKE_A_CHATGPT_SYS_MESSAGE = DEFAULT_SYS_MESSAGE + +def llm_call(input_messages, model:str, api_key_path = "config/key_API.json", system_message = None, temperature = None, json_mode = False) -> list[str, dict]: + """ + This func is used to call LLM + - input: + - input_messages: (not including system message) list of dict like [{"role": "user", "content": "hello"}, {"role": "assistant", "content": "hi"}, ...] + - gpt_model: str like "gpt-3.5-turbo-0613" + - system_message: (valid when input_messages have no sys_message) customized system message, if None, use default system message + - output: + - answer: what gpt returns + - other_infos: dict: + - messages: input_messages + gpt's response, list of dict like [{"role": "user", "content": "hello"}, {"role": "assistant", "content": "hi"}, ...] + - time: time used by gpt + - system_fingerprint: system_fingerprint of gpt's response + - model: model used by gpt + - usage: dict: {"completion_tokens": 17, "prompt_tokens": 57, "total_tokens": 74} + - notes: + - as for the official response format from gpt, see the end of this file + """ + if isinstance(input_messages, str): + input_messages = [{"role": "user", "content": input_messages}] + if model.startswith("claude"): + output = claude_call(input_messages, model, api_key_path, system_message, temperature, json_mode) + elif model.startswith("gpt") or model.startswith("qwen"): + output = gpt_call(input_messages, model, api_key_path, system_message, temperature, json_mode) + else: + raise ValueError("model %s is not supported."%(model)) + llm_manager.update_usage(output[1]["usage"]["prompt_tokens"], output[1]["usage"]["completion_tokens"], model) + return output + + +# def gpt_call(input_messages, model, api_key_path, system_message = None, temperature = None, json_mode = False): +# """ +# This func is used to call gpt +# - input: +# - input_messages: (not including system message) list of dict like [{"role": "user", "content": "hello"}, {"role": "assistant", "content": "hi"}, ...] +# - gpt_model: str like "gpt-3.5-turbo-0613" +# - system_message: (valid when input_messages have no sys_message) customized system message, if None, use default system message +# - output: +# - answer: what gpt returns +# - other_infos: dict: +# - messages: input_messages + gpt's response, list of dict like [{"role": "user", "content": "hello"}, {"role": "assistant", "content": "hi"}, ...] +# - time: time used by gpt +# - system_fingerprint: system_fingerprint of gpt's response +# - model: model used by gpt +# - usage: dict: {"completion_tokens": 17, "prompt_tokens": 57, "total_tokens": 74} +# - notes: +# - as for the official response format from gpt, see the end of this file +# """ +# client = enter_api_key(api_key_path) +# # system message +# has_sysmessage = False +# for message in input_messages: +# if message["role"] == "system": +# has_sysmessage = True +# break +# if not has_sysmessage: +# if system_message is None: +# messages = [{"role": "system", "content": DEFAULT_SYS_MESSAGE}] +# else: +# messages = [{"role": "system", "content": system_message}] +# else: +# messages = [] +# messages.extend(input_messages) +# # other parameters +# more_completion_kwargs = {} +# if temperature is not None: +# more_completion_kwargs["temperature"] = temperature +# if json_mode: +# if not model in JSON_MODELS: +# more_completion_kwargs["response_format"] = {"type": "json_object"} +# # call gpt +# with Timer(print_en=False) as gpt_response: +# completion = client.chat.completions.create( +# model=model, +# messages=messages, +# **more_completion_kwargs +# ) +# answer = completion.choices[0].message.content +# messages.append({"role": "assistant", "content": answer}) +# time = round(gpt_response.interval, 2) +# system_fingerprint = completion.system_fingerprint +# usage = {"completion_tokens": completion.usage.completion_tokens, "prompt_tokens": completion.usage.prompt_tokens, "total_tokens": completion.usage.total_tokens} +# model = completion.model +# other_infos = {"messages": messages, "time": time, "system_fingerprint": system_fingerprint, "model": model, "usage": usage} +# # return answer, messages, time, system_fingerprint +# return answer, other_infos + + + +def gpt_call(input_messages, model, api_key_path, system_message=None, temperature=None, json_mode=False): + """ + GPT Call with Timeout Protection (10min connection, 30min read) + """ + # 1. System Message 处理 (保持原逻辑) + has_sysmessage = False + for message in input_messages: + if message["role"] == "system": + has_sysmessage = True + break + # if not has_sysmessage: + # sys_content = system_message if system_message is not None else "You are a helpful assistant." + # messages = [{"role": "system", "content": sys_content}] + # else: + # messages = [] + # messages.extend(input_messages) + if not has_sysmessage: + sys_content = system_message if system_message is not None else "You are a helpful assistant." + # [修复] 确保 sys_content 是字符串 + if not isinstance(sys_content, str): + print(f" [Warning] system_message is not string, got type: {type(sys_content)}") + sys_content = str(sys_content) + messages = [{"role": "system", "content": sys_content}] + else: + messages = [] + messages.extend(input_messages) + + # [新增] 验证消息格式 + for i, msg in enumerate(messages): + if not isinstance(msg.get("content"), str): + print(f" [Error] messages[{i}]['content'] is not string!") + print(f" Type: {type(msg.get('content'))}") + print(f" Value: {msg.get('content')}") + # 修复:转换为字符串 + messages[i]["content"] = str(msg.get("content")) + + # 2. 参数准备 (保持原逻辑) + more_completion_kwargs = {} + if temperature is not None: + more_completion_kwargs["temperature"] = temperature + if json_mode and "gpt" in model: # Qwen 有时不支持 json_object,加个判断 + more_completion_kwargs["response_format"] = {"type": "json_object"} + + # 3. Client 初始化 (必须在这里重写以注入 httpx 超时) + api_key = "" + base_url = None + + # === 读取 Key (这里必须手动读,因为我们要配置 timeout) === + try: + with open(api_key_path, 'r') as f: + keys = json.load(f) + + if model.startswith("qwen"): + api_key = keys.get("dashscope") # 确保你的 json 里有 "dashscope" + base_url = "https://dashscope.aliyuncs.com/compatible-mode/v1" + else: + api_key = keys.get("openai") or keys.get("gpt") # 兼容 "openai" 或 "gpt" 键名 + + if not api_key: + print(f"❌ [Error] API Key not found in {api_key_path} for model {model}!") + return "", {} + + except Exception as e: + print(f"❌ [Error] Failed to load API Key: {e}") + return "", {} + + # === 配置超时 === + http_client = httpx.Client( + timeout=httpx.Timeout( + connect=300.0, # 5分钟连不上就重试 + read=1800.0, # 30分钟生成不完才断 (给足时间) + write=60.0, + pool=60.0 + ) + ) + + client = openai.OpenAI( + api_key=api_key, + base_url=base_url, + http_client=http_client + ) + + # 4. 调用循环 + MAX_RETRIES = 5 + answer = "" + system_fingerprint = "" + usage = {} + time_used = 0.0 + + for attempt in range(MAX_RETRIES): + try: + with Timer(print_en=False) as gpt_response: + completion = client.chat.completions.create( + model=model, + messages=messages, + **more_completion_kwargs + ) + + answer = completion.choices[0].message.content + system_fingerprint = completion.system_fingerprint + usage = { + "completion_tokens": completion.usage.completion_tokens, + "prompt_tokens": completion.usage.prompt_tokens, + "total_tokens": completion.usage.total_tokens + } + + time_used = round(gpt_response.interval, 2) + + # 调试打印,确认成功 + if answer: + print(f" [LLM] Success. Time: {time_used}s. Length: {len(answer)}") + + break # 成功则跳出 + + except httpx.ConnectTimeout: + print(f"[Timeout] Connection failed (>5min). Retrying {attempt+1}/{MAX_RETRIES}...") + time.sleep(5) + except httpx.ReadTimeout: + print(f" [Timeout] Generation too slow (>30min). Retrying {attempt+1}/{MAX_RETRIES}...") + time.sleep(5) + # except Exception as e: + # print(f" [Error] Attempt {attempt+1} failed: {e}") + # time.sleep(5) + except Exception as e: + error_msg = str(e) + if 'RequestTimeOut' in error_msg or '500' in error_msg: + wait_time = 15 * (attempt + 1) # ✅ 服务端超时特殊处理 + print(f"🔄 [Server Timeout] API server busy. Retrying...") + time.sleep(wait_time) + else: + wait_time = 5 * (attempt + 1) # ✅ 指数退避 + print(f"⚠️ [Error] Attempt {attempt+1} failed: {e}") + time.sleep(wait_time) + + # 5. 返回结果 (保持你的原格式) + if answer: + messages.append({"role": "assistant", "content": answer}) + else: + # 失败兜底,防止外部报错 + usage = {"completion_tokens": 0, "prompt_tokens": 0, "total_tokens": 0} + + other_infos = { + "messages": messages, + "time": time_used, + "system_fingerprint": system_fingerprint, + "model": model, + "usage": usage + } + + return answer, other_infos + +def claude_call(input_messages, model, api_key_path, system_message = None, temperature = None, json_mode = False): + """ + This func is used to call gpt + #### input: + - input_messages: (not including system message) list of dict like [{"role": "user", "content": "hello"}, {"role": "assistant", "content": "hi"}, ...] + - gpt_model: str like "gpt-3.5-turbo-0613" + - config: config object + - system_message: (valid when input_messages have no sys_message) customized system message, if None, use default system message + #### output: + - answer: what gpt returns + - other_infos: dict: + - messages: input_messages + gpt's response, list of dict like [{"role": "user", "content": "hello"}, {"role": "assistant", "content": "hi"}, ...] + - time: time used by gpt + - system_fingerprint: system_fingerprint of gpt's response + - model: model used by gpt + - usage: dict: {"completion_tokens": 17, "prompt_tokens": 57, "total_tokens": 74} + #### notes: + as for the official response format from gpt, see the end of this file + """ + client = enter_api_key(api_key_path, provider="anthropic") + prefill = None + # system message + has_sysmessage = False + for message in input_messages: + if message["role"] == "system": + has_sysmessage = True + break + if not has_sysmessage: + if system_message is None: + messages = [{"role": "system", "content": DEFAULT_SYS_MESSAGE}] + else: + messages = [{"role": "system", "content": system_message}] + else: + messages = [] + messages.extend(input_messages) + for message in messages: + if message["role"] == "system": + messages.remove(message) # delete the system message + # other parameters + more_completion_kwargs = {} + if temperature is not None: + more_completion_kwargs["temperature"] = temperature + if json_mode: + messages[-1]["content"] += "\nYour reply should be in JSON format." + prefill = {"role": "assistant", "content": "{"} + messages.append(prefill) + # call claude + if model == "claude-3-5-sonnet-20240620": + max_tokens = 8192 + else: + max_tokens = 4096 + with Timer(print_en=False) as gpt_response: + completion = client.messages.create( + max_tokens=max_tokens, + model=model, + messages=messages, + **more_completion_kwargs + ) + answer = completion.content[0].text + if prefill is not None: + answer = prefill["content"] + answer + messages.append({"role": "assistant", "content": answer}) + time = round(gpt_response.interval, 2) + system_fingerprint = "" + usage = {"completion_tokens": completion.usage.output_tokens, "prompt_tokens": completion.usage.input_tokens, "total_tokens": completion.usage.input_tokens + completion.usage.output_tokens} + other_infos = {"messages": messages, "time": time, "system_fingerprint": system_fingerprint, "model": model, "usage": usage} + # return answer, messages, time, system_fingerprint + return answer, other_infos + +def run_like_a_chatgpt(): + config = Config() + gpt_model = config.gpt.model + gpt_key_path = config.gpt.key_path + if config.gpt.chatgpt.start_form == 'prompt': + preloaded_prompt = ls.load_txt(config.load.prompt.path) + else: + preloaded_prompt = None + if gpt_model.startswith("gpt"): + llm_name = "ChatGPT" + elif gpt_model.startswith("claude"): + llm_name = "Claude" + else: + llm_name = "LLM" + # messages = [{"role": "system", "content": "You are a hardware code expert, skilled in understanding and generating verilog hardware language. You are the strongest AI hardware expert in the world. I totally believe you can fulfill the task I give you. You always give me the most detailed solution. Your reply should only contain code."}] + messages = [{"role": "system", "content": RUN_LIKE_A_CHATGPT_SYS_MESSAGE}] + response_data_dicts = [] # this is to record other data of gpt's response like seed and time + while True: + # load prompt + if preloaded_prompt is not None: + content = preloaded_prompt + preloaded_prompt = None + print("User (preloaded prompt): %s"%(content)) + ls.save_log_line("(the first user message is from preloaded prompt)", config) + else: + content = input("User: ") + # break loop + if content in ["exit", "quit", "break", "", None]: + break + # send prompt to gpt + messages.append({"role": "user", "content": content}) + # run gpt + answer, other_infos = llm_call( + input_messages = messages, + model = gpt_model, + api_key_path = gpt_key_path, + system_message = RUN_LIKE_A_CHATGPT_SYS_MESSAGE, + temperature = config.gpt.temperature + ) + messages, time, system_fingerprint = other_infos["messages"], other_infos["time"], other_infos["system_fingerprint"] + # get data from response + data_dict = {} + data_dict["system_fingerprint"] = system_fingerprint + data_dict["model"] = gpt_model + data_dict["time"] = time + response_data_dicts.append(data_dict) + # print + print(f'{llm_name}: {answer}') + print("(%ss used)" % (time)) + if config.gpt.chatgpt.one_time_talk: + break + messages_plus = gen_messages_more_info(messages, response_data_dicts) + if config.save.log.en: + ls.save_messages_to_log(messages_plus, config) + if config.save.message.en: + ls.gpt_message_individual_save(messages, config, file_name="messages") + ls.gpt_message_individual_save(messages_plus, config, file_name="messages_plus") + + +# def enter_api_key(api_key_path, provider="openai"): +# if provider == "openai": +# key = ls.load_json_dict(api_key_path)["OPENAI_API_KEY"] +# client = OpenAI(api_key=key) +# elif provider == "anthropic": +# key = ls.load_json_dict(api_key_path)["ANTHROPIC_API_KEY"] +# client = Anthropic(api_key=key) +# else: +# raise ValueError("provider %s is not supported."%(provider)) +# return client + + +def enter_api_key(api_key_path, provider="openai"): + if provider == "openai": + # 1. 读取文件里的 Key (刚才第一步改好的) + key = ls.load_json_dict(api_key_path)["OPENAI_API_KEY"] + + # 2. 关键:从环境变量获取 Base URL + # 如果没有这一步,请求会发给 openai.com 导致 401 + base_url = os.environ.get("OPENAI_BASE_URL") + + if base_url: + print(f"DEBUG: Connecting to {base_url}...") # 加一行打印方便调试 + client = OpenAI(api_key=key, base_url=base_url) + else: + client = OpenAI(api_key=key) + elif provider == "anthropic": + # Claude 部分保持原样 + key = ls.load_json_dict(api_key_path)["ANTHROPIC_API_KEY"] + client = Anthropic(api_key=key) + else: + raise ValueError("provider %s is not supported."%(provider)) + return client + + +def gen_messages_more_info(original_messages, response_data_dicts): + # additional info only at: role = "assistant" + messages = copy.deepcopy(original_messages) + idx_response = 0 + for i in range(len(messages)): + if messages[i]["role"] == "assistant": + # messages[i].extend(response_data_dicts[idx_response]) # wrong syntax + messages[i] = {**messages[i], **response_data_dicts[idx_response]} + idx_response += 1 + # add idx to each message + for i in range(len(messages)): + messages[i]["idx"] = i + return messages + + +def dalle3(): + """ + This function hasn't been well packaged + now we have the free dalle3 application: microsoft - copilot + """ + def download_image(url, folder_path): + response = requests.get(url) + file_path = os.path.join(folder_path, os.path.basename(url)) + with open(file_path, "wb") as file: + file.write(response.content) + return file_path + + model_name = "dall-e-3" + image_size = "1024x1024" # 1792x1024, 1024x1024, 1024x1792 + download_folder = r"saves/dalle3/" + os.makedirs(download_folder, exist_ok=True) + + while True: + name = input("please name the generated figure (\"exit\" to exit): ") + if name == "exit": + break + prompt = input("please input the prompt(\"exit\" to exit): ") + if prompt == "exit": + break + + try: + # num_images = int(input("please input the number of figures (default=1):") or "1") + num_images = 1 + print("generating your figure...") + # response = requests.post( + # "https://api.openai-proxy.org/v1/images/generations", + # headers={"Authorization": ""}, + # json={"model": model_name, "size": image_size, "prompt": prompt, "n": num_images}, + # ) + client = enter_api_key('config/key_API.json') + response = client.images.generate( + model=model_name, + prompt=prompt, + size=image_size, + quality="standard", + n=num_images, + ) + # response.raise_for_status() + # data = response.json()["data"] + + image_url = response.data[0].url + # the name should end with .png + file_name = name + ".png" + file_path = download_image(image_url, download_folder) + new_file_path = os.path.join(download_folder, file_name) + os.rename(file_path, new_file_path) + print("figure was downloaded to %s" %(new_file_path)) + + + # file_path = download_image(image_url, download_folder) + # print("图片已下载至:", file_path) + + + # current_time = datetime.now(timezone.utc) + timedelta(hours=8) + # current_time_str = current_time.strftime("%Y%m%d-%H%M") + + # for i, image in enumerate(data): + # image_url = image["url"] + # file_name = current_time_str + f"-{str(i+1).zfill(3)}.png" + # file_path = download_image(image_url, download_folder) + # new_file_path = os.path.join(download_folder, file_name) + # os.rename(file_path, new_file_path) + # print("图片已下载至:", new_file_path) + + except requests.exceptions.HTTPError as err: + print("Request Error: ", err.response.text) + + except Exception as e: + print("Error: ", str(e)) + + + +############### utils of gpt ############### +def num_tokens_from_string(string: str, model_name="gpt-4") -> int: + """ + Returns the number of tokens in a single text string. + https://github.com/openai/openai-cookbook/blob/main/examples/How_to_count_tokens_with_tiktoken.ipynb + """ + try: + encoding = tiktoken.encoding_for_model(model_name) + except KeyError: + encoding = tiktoken.get_encoding("cl100k_base") + num_tokens = len(encoding.encode(string)) + return num_tokens + +def num_tokens_from_messages(messages, model="gpt-3.5-turbo-0613"): + """Returns the number of tokens used by a list of messages.""" + try: + encoding = tiktoken.encoding_for_model(model) + except KeyError: + encoding = tiktoken.get_encoding("cl100k_base") + if model in PRICING_MODELS.keys(): + num_tokens = 0 + for message in messages: + num_tokens += 4 # every message follows {role/name}\n{content}\n + for key, value in message.items(): + num_tokens += len(encoding.encode(value)) + if key == "name": # if there's a name, the role is omitted + num_tokens += -1 # role is always required and always 1 token + num_tokens += 2 # every reply is primed with assistant + return num_tokens + else: + raise NotImplementedError(f"""num_tokens_from_messages() is not presently implemented for model {model}. + See https://github.com/openai/openai-python/blob/main/chatml.md for information on how messages are converted to tokens.""") + +# def extract_code(text, code_type): +# """ +# #### function: +# - extract code from text +# #### input: +# - text: str, gpt's response +# - code_type: str, like "verilog" +# #### output: +# - list of found code blocks +# """ +# code_type = code_type.lower() +# start = "```" + code_type +# end = "```" +# verilog_blocks = re.findall(start + r'\s*(.*?)'+ end, text, re.DOTALL) +# if verilog_blocks: +# return verilog_blocks +# else: +# # return [""] +# return [text] + +def extract_code(text, code_type): + """ + [增强版] 能够处理带 verilog 标签、不带标签、甚至带中文解释的情况 + """ + # 打印原始回复,让你在终端直接能看到 Qwen 到底回了什么 (调试神器) + print("\n[DEBUG] Raw LLM Response:\n", text) + print("-" * 30) + + # 1. 尝试匹配 Markdown 代码块 (``` ... ```) + # 正则解释:找 ``` 开头,中间可能跟着语言名(如verilog),然后是内容,最后是 ``` + pattern = r"```(?:\w+)?\s*(.*?)```" + matches = re.findall(pattern, text, re.DOTALL) + + if matches: + # 如果找到了代码块,只返回代码块里的内容 + return matches + + # 2. 如果没找到 Markdown 标记,可能是纯代码,但也可能包含 "module" 关键字 + # 我们尝试只提取 module ... endmodule 之间的内容 (简易版) + if "module" in text and "endmodule" in text: + # 这是一个非常粗暴但有效的兜底:找到第一个 module 和最后一个 endmodule + start_idx = text.find("module") + end_idx = text.rfind("endmodule") + len("endmodule") + clean_code = text[start_idx:end_idx] + return [clean_code] + + # 3. 实在没招了,返回原始内容 (之前的逻辑) + return [text] + + + +def get_dict_from_gpt_json(gpt_json_string): + """ + - this function is used to get the dict from the gpt json string + """ + gpt_json_string = gpt_json_string.replace("```json", "").replace("```", "").strip() + print(gpt_json_string) + return json.loads(gpt_json_string) + +def cost_calculator(usages:list, model="gpt-4-0125-preview"): + """ + - this function is used to calculate the price of gpt + - usage: list of dicts, [{"completion_tokens": 17, "prompt_tokens": 57, "total_tokens": 74}, ...] + + """ + if model not in PRICING_MODELS: + raise ValueError(f"model {model} is not supported in the pricing calculator.") + price = 0 + for usage in usages: + price += usage["prompt_tokens"] * PRICING_MODELS[model][0] / 1000.0 + usage["completion_tokens"] * PRICING_MODELS[model][1] / 1000.0 + return price + +def message_to_conversation(messages): + """ + - this function is used to convert messages to conversation + """ + conversation = "" + for message in messages: + if message["role"] == "system": + conversation += "############################## conversation begin ##############################\n" + conversation += '########## %s ##########\n%s\n\n' % (message['role'], message['content']) + return conversation + +class LLM_Manager: + _instance = None + _initialized = False + + def __new__(cls, *args, **kwargs): + if not cls._instance: + cls._instance = super(LLM_Manager, cls).__new__(cls) + return cls._instance + + def __init__(self, api_key="config/key_API.json") -> None: + if not self._initialized: + # total + self.tokens_in_total = 0 + self.tokens_out_total = 0 + self.tokens_both_total = 0 + self.cost_total = 0 + # section + self.tokens_in_section = 0 + self.tokens_out_section = 0 + self.tokens_both_section = 0 + self.cost_section = 0 + # dict {"model1": {}, "model2": {}, ...} + self.usage_info = {} + # chat + self._llm_model_now = None + self._temperature = None + self.messages = [{"role": "system", "content": DEFAULT_SYS_MESSAGE}] + if not os.path.exists(api_key): + raise ValueError(f"api key path {api_key} is not valid.") + self._api_key_path = api_key + + def update_usage(self, tokens_in:int, tokens_out:int, model:str): + cost = tokens_in * PRICING_MODELS[model][0] / 1000.0 + tokens_out * PRICING_MODELS[model][1] / 1000.0 + # dict + if model not in self.usage_info.keys(): + self.usage_info[model] = {"tokens_in": 0, "tokens_out": 0, "tokens_both": 0, "cost": 0} + self.usage_info[model]["tokens_in"] += tokens_in + self.usage_info[model]["tokens_out"] += tokens_out + self.usage_info[model]["tokens_both"] += tokens_in + tokens_out + self.usage_info[model]["cost"] += cost + # total + self.tokens_in_total += tokens_in + self.tokens_out_total += tokens_out + self.tokens_both_total += tokens_in + tokens_out + self.cost_total += cost + # section + self.tokens_in_section += tokens_in + self.tokens_out_section += tokens_out + self.tokens_both_section += tokens_in + tokens_out + self.cost_section += cost + + def new_section(self): + """ + new usage section (only reset the tokens and cost of the section) + """ + self.tokens_in_section = 0 + self.tokens_out_section = 0 + self.tokens_both_section = 0 + self.cost_section = 0 + + def set_model(self, model:str): + self._llm_model_now = model + + def set_temperature(self, temperature:float): + self._temperature = temperature + + def chat(self, prompt:str, clear_mem:bool=False, model:str=None, temperature:float=None, sys_prompt:str=DEFAULT_SYS_MESSAGE)->str: + model = self._llm_model_now if model is None else model + temperature = self._temperature if temperature is None else temperature + if clear_mem: + self.messages = [{"role": "system", "content": sys_prompt}] + self.messages.append({"role": "user", "content": prompt}) + answer, other_infos = llm_call( + input_messages = self.messages, + model = model, + api_key_path = self._api_key_path, + temperature = temperature + ) # usage already updated in llm_call + # update messages + self.messages = other_infos["messages"] + return answer + + +llm_manager = LLM_Manager() + + + +if __name__ == "__main__": + # print("GPT_call.py does not support running as a main file.") + print('we are testing dalle3') + dalle3() + + +""" +(see more in https://platform.openai.com/docs/guides/text-generation/chat-completions-api) +An example Chat Completions API response looks as follows: +{ + "choices": [ + { + "finish_reason": "stop", + "index": 0, + "message": { + "content": "The 2020 World Series was played in Texas at Globe Life Field in Arlington.", + "role": "assistant" + } + } + ], + "created": 1677664795, + "id": "chatcmpl-7QyqpwdfhqwajicIEznoc6Q47XAyW", + "model": "gpt-3.5-turbo-0613", + "object": "chat.completion", + "usage": { + "completion_tokens": 17, + "prompt_tokens": 57, + "total_tokens": 74 + } +} +""" \ No newline at end of file diff --git a/__pycache__/LLM_call.cpython-312.pyc b/__pycache__/LLM_call.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..c9b856e87d2585827a7e2ace2373faf01ecf7f9d GIT binary patch literal 27614 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 literal 0 HcmV?d00001 diff --git a/__pycache__/iverilog_call.cpython-312.pyc b/__pycache__/iverilog_call.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..739f3c7f0f620c8b65da2f8d71404fdae5f8f1c6 GIT binary patch literal 6589 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 literal 0 HcmV?d00001 diff --git a/__pycache__/loader_saver.cpython-312.pyc b/__pycache__/loader_saver.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b2fa3e3dc07328562e6c428d7985a6259eba4b12 GIT binary patch literal 29431 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 BUbg@M literal 0 HcmV?d00001 diff --git a/__pycache__/run_tbgen.cpython-312.pyc b/__pycache__/run_tbgen.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..5e4f81b6d1a431e3a8931285bc2674a68b893b96 GIT binary patch literal 5639 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 YsA-~f>v+kwaq5ZB$?db-k(R~(1+0`Of&c&j literal 0 HcmV?d00001 diff --git a/analyze.py b/analyze.py new file mode 100644 index 0000000..95020ed --- /dev/null +++ b/analyze.py @@ -0,0 +1,1059 @@ +""" +Description : analyze the output from autoline mode. +Author : Ruidi Qiu (r.qiu@tum.de) +Time : 2023/12/12 17:35:00 +LastEdited : 2024/9/17 23:35:03 +""" + +import loader_saver as ls +import utils.utils as utils +from data.probset import dictlist, HDLBitsProbset, muti_dictlist +from LLM_call import PRICING_MODELS +import os +import math + +LOOSE_FACTOR = 0.8 +DEFAULT_SAVING_DIR = "analysis" +DEFAULT_LOG_NAME = "analyze_out.log" +DEFAULT_LOG_PATH = os.path.join(DEFAULT_SAVING_DIR, DEFAULT_LOG_NAME) +K_LIST = [1] + + + + + +# insert your Chatbench_RunInfo.json's path here to re-analyze the data, then run this file directly. the result will be saved in analysis/analyze_out.log +CHATBENCH_RUNINFO_PATH = "saves_inEDA/DATE25/Main_Results/CorrectBench/disc_70wrong_25correct_20240831_181427/Chatbench_RunInfo.json" + + +# this is for multiple directories' analysis, insert the directory path here and change the main function to regular_multiA_main, then run the file directly. the result will be saved in analysis/analyze_out.log +MULTI_DIR = "saves_inEDA/DATE25/Main_Results/CorrectBench" + + +def main(): + # diy_main() + regular_main() + # regular_multiA_main() + +def diy_main(): + + # find the passed with correction + Chatbench_RunInfo = ls.load_json_dict(CHATBENCH_RUNINFO_PATH) + # analyze the data + analyzer = Analyzer(Chatbench_RunInfo) + analyzer.run() + for i in analyzer.data: + if i.get("TB_corrected", False) and analyzer.Eval2_pass(i): + print(i["task_id"]) + # save the result to a txt file + # with open(DEFAULT_LOG_PATH, "w") as f: + # f.write(analyzer.messages) + # # also, write the current time + # f.write("analysis time: %s\n" % (utils.get_time())) + + # with open(DEFAULT_LOG_PATH, "w") as f: + # f.write("analysis time: %s\n" % (utils.get_time())) + # # task_eval2passtimes_analyze() + # full_analyzer = Analyzer(ls.load_json_dict(CHATBENCH_RUNINFO_PATH)) + # full_analyzer.run() + # with open(DEFAULT_LOG_PATH, "a") as f: + # f.write(full_analyzer.messages) + # analyze_subset(HDLBitsProbset("data/HDLBits/HDLBits_circuit_type.jsonl", filter_content={"circuit_type": "CMB"}), CHATBENCH_RUNINFO_PATH, "full CMB") + # analyze_subset(HDLBitsProbset("data/HDLBits/HDLBits_circuit_type.jsonl", filter_content={"circuit_type": "SEQ"}), CHATBENCH_RUNINFO_PATH, "full SEQ") + # analyze_subset("data/HDLBits/HDLBits_data_CMB15.jsonl", CHATBENCH_RUNINFO_PATH, "CMB15") + # analyze_subset("data/HDLBits/HDLBits_data_SEQ15.jsonl", CHATBENCH_RUNINFO_PATH, "SEQ15") + + + + k_list = [1] + # Eval_scenchecks = ["Eval0_scencheck", "Eval0_noscencheck", "Eval1_scencheck", "Eval1_noscencheck", "Eval2_scencheck", "Eval2_noscencheck"] + # Eval_scenchecks = ["Eval2b", "Eval2"] + multi_analyzer = MultiAnalyzer(MULTI_DIR, k_list) + # multi_analyzer.exclude_debug = True + multi_analyzer.messages += "\n#################### TOTAL ####################\n" + multi_analyzer.run() + multi_analyzer.get_avg_tokens_one_task() + multi_analyzer.get_avg_pass_by_disc_and_corr() + multi_analyzer.save() + CMB_set = HDLBitsProbset("data/HDLBits/HDLBits_circuit_type.jsonl", filter_content={"circuit_type": "CMB"}) + SEQ_set = HDLBitsProbset("data/HDLBits/HDLBits_circuit_type.jsonl", filter_content={"circuit_type": "SEQ"}) + CMB_tasks = CMB_set.task_id_list + SEQ_tasks = SEQ_set.task_id_list + multi_analyzer_CMB = MultiAnalyzer(MULTI_DIR, k_list) + # multi_analyzer_CMB.exclude_debug = True + multi_analyzer_CMB.del_items(SEQ_tasks, del_by_list=True) + # print(multi_analyzer_CMB.access("total_num")) + multi_analyzer_CMB.messages += "\n#################### CMB ####################\n" + multi_analyzer_CMB.run() + multi_analyzer_CMB.get_avg_tokens_one_task() + multi_analyzer_CMB.get_avg_pass_by_disc_and_corr() + multi_analyzer_CMB.save(os.path.join(DEFAULT_SAVING_DIR, "CMB_" + DEFAULT_LOG_NAME)) + multi_analyzer_SEQ = MultiAnalyzer(MULTI_DIR, k_list) + # multi_analyzer_SEQ.exclude_debug = True + multi_analyzer_SEQ.del_items(CMB_tasks, del_by_list=True) + multi_analyzer_SEQ.messages += "\n#################### SEQ ####################\n" + multi_analyzer_SEQ.run() + multi_analyzer_SEQ.get_avg_tokens_one_task() + multi_analyzer_SEQ.get_avg_pass_by_disc_and_corr() + multi_analyzer_SEQ.save(os.path.join(DEFAULT_SAVING_DIR, "SEQ_" + DEFAULT_LOG_NAME)) + + # multi_analyzer_SEQ.save(os.path.join(DEFAULT_SAVING_DIR, "SEQ_" + DEFAULT_LOG_NAME)) + + # show the pass num for seq15 of each try + # multi_analyzer = MultiAnalyzer(MULTI_DIR) + # multi_analyzer.exclude_debug = True + # SEQ15_set = HDLBitsProbset("data/HDLBits/HDLBits_data_SEQ15.jsonl") + # SEQ15_tasks = SEQ15_set.task_id_list + # multi_analyzer.del_items(SEQ15_tasks, del_by_list=False) + # multi_analyzer.renew_result_dict() + # multi_analyzer.run() + # print(multi_analyzer.access("total_num")) + # print(multi_analyzer.access("fullpass_num_nodebug")) + # print(sum(multi_analyzer.access("fullpass_num_nodebug"))) + # for task in multi_analyzer.result_dict.data: + # print(task["task_id"] + ": " + str(round(task["Eval2_pass_at_1"], 2))) + + # k_list = [1,5,10] + # multi_analyzer = MultiAnalyzer(MULTI_DIR, k_list) + # data_list = multi_analyzer.dictlists + # CMB_num_list = [] + # for data in data_list: + # CMB_num = 0 + # for i in data.data: + # if i.get("circuit_type", "NO data") == "CMB": + # CMB_num += 1 + # CMB_num_list.append(CMB_num) + # print(CMB_num_list) + + # k_list = [1,5,10] + # multi_analyzer = MultiAnalyzer(MULTI_DIR, k_list) + # # multi_analyzer.exclude_debug = True + # multi_analyzer.messages += "\n#################### TOTAL ####################\n" + # multi_analyzer.run() + # multi_analyzer.save() + # CMB_set = HDLBitsProbset("data/HDLBits/HDLBits_circuit_type.jsonl", filter_content={"circuit_type": "CMB"}) + # SEQ_set = HDLBitsProbset("data/HDLBits/HDLBits_circuit_type.jsonl", filter_content={"circuit_type": "SEQ"}) + # CMB_tasks = CMB_set.task_id_list + # SEQ_tasks = SEQ_set.task_id_list + # multi_analyzer_CMB = MultiAnalyzer(MULTI_DIR, k_list) + # # multi_analyzer_CMB.exclude_debug = True + # multi_analyzer_CMB.del_items(SEQ_tasks, del_by_list=True) + # # print(multi_analyzer_CMB.access("total_num")) + # multi_analyzer_CMB.messages += "\n#################### CMB ####################\n" + # multi_analyzer_CMB.run() + # multi_analyzer_CMB.save(os.path.join(DEFAULT_SAVING_DIR, "CMB_" + DEFAULT_LOG_NAME)) + # multi_analyzer_SEQ = MultiAnalyzer(MULTI_DIR, k_list) + # # multi_analyzer_SEQ.exclude_debug = True + # multi_analyzer_SEQ.del_items(CMB_tasks, del_by_list=True) + # multi_analyzer_SEQ.messages += "\n#################### SEQ ####################\n" + # multi_analyzer_SEQ.run() + # multi_analyzer_SEQ.save(os.path.join(DEFAULT_SAVING_DIR, "SEQ_" + DEFAULT_LOG_NAME)) + + # k_list = [1,3,5,10] + # multi_analyzer = MultiAnalyzer(MULTI_DIR, k_list) + # multi_analyzer.exclude_debug = True + # multi_analyzer.messages += "\n#################### TOTAL ####################\n" + # multi_analyzer.run() + # multi_analyzer.save() + # CMB_set = HDLBitsProbset("data/HDLBits/HDLBits_circuit_type.jsonl", filter_content={"circuit_type": "CMB"}) + # SEQ_set = HDLBitsProbset("data/HDLBits/HDLBits_circuit_type.jsonl", filter_content={"circuit_type": "SEQ"}) + # CMB_tasks = CMB_set.task_id_list + # SEQ_tasks = SEQ_set.task_id_list + # multi_analyzer_CMB = MultiAnalyzer(MULTI_DIR, k_list) + # multi_analyzer_CMB.del_items(SEQ_tasks, del_by_list=True) + # multi_analyzer_CMB.exclude_debug = True + # multi_analyzer_CMB.messages += "\n#################### CMB ####################\n" + # multi_analyzer_CMB.run() + # multi_analyzer_CMB.save(os.path.join(DEFAULT_SAVING_DIR, "CMB_" + DEFAULT_LOG_NAME)) + # multi_analyzer_SEQ = MultiAnalyzer(MULTI_DIR, k_list) + # multi_analyzer_SEQ.del_items(CMB_tasks, del_by_list=True) + # multi_analyzer_SEQ.exclude_debug = True + # multi_analyzer_SEQ.messages += "\n#################### SEQ ####################\n" + # multi_analyzer_SEQ.run() + # multi_analyzer_SEQ.save(os.path.join(DEFAULT_SAVING_DIR, "SEQ_" + DEFAULT_LOG_NAME)) + + + # Chatbench_RunInfo = ls.load_json_dict(CHATBENCH_RUNINFO_PATH) + # analyzer = Analyzer(Chatbench_RunInfo) + + + # # task 2024/04/25 17:09:39, extract SEQ15 info from SEQ + # Chatbench_RunInfo = ls.load_json_dict(CHATBENCH_RUNINFO_PATH) + # seq15 = HDLBitsProbset("data/HDLBits/HDLBits_data_SEQ15.jsonl") + # seq15_taskids = seq15.task_id_list + # analyzer = Analyzer(Chatbench_RunInfo) + # analyzer.del_items(seq15_taskids, False) + # # analyzer.filter({"debug_iter_iv": 0}) + # analyzer.run() + # with open(DEFAULT_LOG_PATH, "w") as f: + # f.write(analyzer.messages) + # # also, write the current time + # f.write("analysis time: %s\n" % (utils.get_time())) + + # Chatbench_RunInfo = ls.load_json_dict(CHATBENCH_RUNINFO_PATH) + # # analyze the data + # analyzer = Analyzer(Chatbench_RunInfo) + # analyzer.filter({"debug_iter_iv": 0}) + # analyzer.run() + # # save the result to a txt file + # with open(DEFAULT_LOG_PATH, "w") as f: + # f.write(analyzer.messages) + # # also, write the current time + # f.write("analysis time: %s\n" % (utils.get_time())) + pass + +def regular_multiA_main(): + multi_analyzer = MultiAnalyzer(MULTI_DIR) + multi_analyzer.run() + multi_analyzer.save() + +def regular_main(): + Chatbench_RunInfo = ls.load_json_dict(CHATBENCH_RUNINFO_PATH) + # analyze the data + analyzer = Analyzer(Chatbench_RunInfo) + analyzer.run() + # save the result to a txt file + with open(DEFAULT_LOG_PATH, "w") as f: + f.write(analyzer.messages) + # also, write the current time + f.write("analysis time: %s\n" % (utils.get_time())) + +def analyze_subset(subset:str|HDLBitsProbset, runinfo_path, subset_name=""): + """ + this function is used to only analyze a subset of the runinfo data + - subset (only the task_ids are needed): + - str: path of the subset + - HDLBitsProbset: the subset + - runinfo_path: path of the Chatbench_RunInfo.json + - subset_name: the name of the subset + """ + if isinstance(subset, str): + # path, load the subset + subset = HDLBitsProbset(subset) + elif isinstance(subset, HDLBitsProbset): + subset = subset + else: + raise TypeError("subset should be a path or a HDLBitsProbset") + subset_tasks = subset.task_id_list + analyzer = Analyzer(ls.load_json_dict(runinfo_path)) + analyzer.del_items(subset_tasks, False) + analyzer.out_txt += f"\n#################### {subset_name} ####################\n" + analyzer.run() + with open(DEFAULT_LOG_PATH, "a") as f: + f.write(analyzer.messages) + f.write("analysis time: %s\n" % (utils.get_time())) + +class Analyzer(HDLBitsProbset): + def __init__(self, Chatbench_RunInfo, pricing_model="gpt-4o-2024-05-13"): + super().__init__() + self.data = Chatbench_RunInfo + self.check_existance() + self.pricing_model = pricing_model + self.out_txt = "" + self.loose_factor = LOOSE_FACTOR + + def run(self): + self.out_txt += "\n########## Analyze of Chatbench_RunInfo ##########\n" + + self.out_txt += "\n#### pass numbers:\n" + if self.Eval2b_exist: + self.out_txt += "Eval2b: %d\n" % self.Eval2bpass_num + self.out_txt += "Eval2 : %d\n" % self.fullpass_num + self.out_txt += "Eval1 : %d\n" % self.Eval1pass_num + self.out_txt += "Eval0 : %d\n" % self.Eval0pass_num + self.out_txt += "total : %d " % self.total_num + self.out_txt += "(Failed: %d)\n" % (self.total_num - self.Eval0pass_num) + if self.reboot_times_exist: + self.out_txt += "passed TB by autoline reboot action (from TB3_check): %d\n" % self.autoline_reboot_task_num + if self.TB_corrected_exist: + self.out_txt += "\npassed TB by functional corrector: %d\n" % self.corrected_num + + # === [新增] CGA Coverage 统计 === + # 计算平均覆盖率 + total_coverage = 0.0 + max_coverage = 0.0 + min_coverage = 100.0 + has_cov_data = False + + for task in self.data: + cov = task.get("coverage", 0.0) + if cov > 0: has_cov_data = True + total_coverage += cov + if cov > max_coverage: max_coverage = cov + if cov < min_coverage: min_coverage = cov + + avg_cov = total_coverage / self.total_num if self.total_num > 0 else 0.0 + + self.out_txt += "\n#### CGA Coverage Info:\n" + self.out_txt += "Average Coverage : %.2f%%\n" % avg_cov + if has_cov_data: + self.out_txt += "Max Coverage : %.2f%%\n" % max_coverage + self.out_txt += "Min Coverage : %.2f%%\n" % min_coverage + else: + self.out_txt += "(No coverage data found in JSON)\n" + # =============================== + # self.out_txt += self.get_avg_debug_iter_on_sim_pass_with_debug()[-1] + # self.out_txt += self.get_debug_failed_num()[-1] + # self.out_txt += self.get_debug_total_pass_num()[-1] + # self.out_txt += self.get_debug_sim_pass_num()[-1] + + self.out_txt += "\n#### tokens and cost:\n" + # self.out_txt += "average prompt tokens: %d\naverage completion tokens: %d\n" % (self.prompt_tokens_num/self.total_num, self.completion_tokens_num/self.total_num) + self.out_txt += "average prompt tokens: %d\n" % (self.prompt_tokens_num / self.total_num) + self.out_txt += "average completion tokens: %d\n" % (self.completion_tokens_num / self.total_num) + self.out_txt += "total cost: %.4f\n" % self.cost + self.out_txt += "average cost: %.4f\n" % self.avg_cost + + self.out_txt += "\n#### time:\n" + self.out_txt += "average time: %.2fs\n" % self.avg_time + + self.out_txt += "\n#### debug info table:\n" + self.out_txt += self.get_debug_infotable() + + self.out_txt += "\n#### Eval2 ratio:\n" + self.out_txt += self.get_eval2_ratio_each_problem() + + # === [新增] 每个任务的覆盖率详情 === + self.out_txt += "\n#### CGA Coverage Detail List:\n" + self.out_txt += f"{'Task ID':<25} | {'Coverage':<10}\n" + self.out_txt += "-" * 40 + "\n" + for task in self.data: + tid = task.get("task_id", "Unknown") + cov = task.get("coverage", 0.0) + self.out_txt += f"{tid:<25} | {cov:.2f}%\n" + # ================================== + if self.Eval2b_exist: + self.out_txt += "\n#### Eval2b ratio:\n" + self.out_txt += self.get_eval2b_ratio_each_problem() + + + # self.get_iv_runing_time_info() + + self.out_txt += "\nloose Eval2 pass metric applied: %s\n\n" % self.loose_factor + # 将生成的 out_txt 同步给 messages 属性,保证 save 时能写入文件 + + + def find_fake_eval0pass(self): + self.filter({"sim_pass": 1}) + task_ids_fake_eval0pass = [] + for i in self.data: + if i.get("Eval1_pass","NO data") == "NO data": + task_ids_fake_eval0pass.append(i["task_id"]) + self.out_txt += "fake Eval0 pass: %d\n" % len(task_ids_fake_eval0pass) + for i in task_ids_fake_eval0pass: + self.out_txt += i + "\n" + + def check_existance(self): + self.Eval2b_exist = False + self.TB_corrected_exist = False + self.reboot_times_exist = False + for i in self.data: + if "Eval2b_pass" in i.keys(): + self.Eval2b_exist = True + if "TB_corrected" in i.keys(): + self.TB_corrected_exist = True + if "reboot_times" in i.keys(): + self.reboot_times_exist = True + if self.Eval2b_exist and self.TB_corrected_exist and self.reboot_times_exist: + break + + # task + def draw_Eval2_histogram(self, figurename="eval2_histogram.png"): + import matplotlib.pyplot as plt + import matplotlib + matplotlib.use('Agg') + ratios = [] + for i in self.data: + if self.Eval0_pass(i) and self.Eval1_pass(i): + # get the numerical ratio + ratio_str = i.get("Eval2_ratio", None) + if ratio_str is None: + continue + numerator, denominator = ratio_str.split("/") + ratio = float(numerator) / float(denominator) + ratios.append(ratio) + # draw histogram of ratios, it will have 10 bars, 0~10%, 10~20%, ..., 90~100% + plt.hist(ratios, bins=10, range=(0,1)) + # save to analysis/eval2_histogram.png + plt.savefig(os.path.join(DEFAULT_SAVING_DIR, figurename)) + plt.close() + + @property + def messages(self): + return self.out_txt + + @property + def total_num(self): + if not hasattr(self, "_total_num"): + self._total_num = len(self.data) + return self._total_num + + @property + def fullpass_num(self): + if not hasattr(self, "_fullpass_num"): + self._fullpass_num = 0 + for i in self.data: + if self.Eval0_pass(i) and i.get("Eval1_pass",0) and self.Eval2_pass(i): + self._fullpass_num += 1 + return self._fullpass_num + + @property + def fullpass_num_nodebug(self): + if not hasattr(self, "_fullpass_num"): + self._fullpass_num = 0 + for i in self.data: + if self.Eval0_pass(i) and i.get("Eval1_pass",0) and self.Eval2_pass(i) and self.debug_iter(i) == 0: + self._fullpass_num += 1 + return self._fullpass_num + + @property + def Eval2bpass_num(self): + if not hasattr(self, "_Eval2bpass_num"): + self._Eval2bpass_num = 0 + if self.Eval2b_exist: + for i in self.data: + if self.Eval0_pass(i) and i.get("Eval1_pass",0) and i.get("Eval2b_pass",0): + self._Eval2bpass_num += 1 + return self._Eval2bpass_num + + @property + def Eval0pass_num(self): + if not hasattr(self, "_Eval0pass_num"): + self._Eval0pass_num = 0 + for i in self.data: + if self.Eval0_pass(i): + self._Eval0pass_num += 1 + return self._Eval0pass_num + + @property + def Eval1pass_num(self): + if not hasattr(self, "_Eval1pass_num"): + self._Eval1pass_num = 0 + for i in self.data: + if self.Eval0_pass(i) and i.get("Eval1_pass",0): + self._Eval1pass_num += 1 + return self._Eval1pass_num + + @property + def corrected_num(self): + if not hasattr(self, "_corrected_num"): + self._corrected_num = 0 + if self.TB_corrected_exist: + for i in self.data: + if i.get("TB_corrected",0) and self.Eval2_pass(i): + self._corrected_num += 1 + return self._corrected_num + + @property + def autoline_reboot_task_num(self): + if not hasattr(self, "_autoline_reboot_task_num"): + self._autoline_reboot_task_num = 0 + if self.reboot_times_exist: + for i in self.data: + if (i.get("reboot_times",0) > 0) and self.Eval2_pass(i): + self._autoline_reboot_task_num += 1 + return self._autoline_reboot_task_num + + @property + def avg_time(self): + if not hasattr(self, "_avg_time"): + time_sum = 0 + for i in self.data: + time_sum += i.get("time",0) + self._avg_time = time_sum / len(self.data) + return self._avg_time + + @property + def tokens_num(self): + if not hasattr(self, "_tokens_num"): + prompt_tokens_sum = 0 + completion_tokens_sum = 0 + for i in self.data: + prompt_tokens_sum += i.get("prompt_tokens",0) + completion_tokens_sum += i.get("completion_tokens",0) + self._prompt_tokens_num = prompt_tokens_sum + self._completion_tokens_num = completion_tokens_sum + self._tokens_num = prompt_tokens_sum + completion_tokens_sum + return self._tokens_num + + @property + def prompt_tokens_num(self): + if not hasattr(self, "_prompt_tokens_num"): + self.tokens_num + return self._prompt_tokens_num + + @property + def completion_tokens_num(self): + if not hasattr(self, "_completion_tokens_num"): + self.tokens_num + return self._completion_tokens_num + + @property + def avg_tokens(self): + if not hasattr(self, "_avg_tokens"): + self._avg_tokens = self.tokens_num / self.total_num + return self._avg_tokens + + @property + def cost(self): + if not hasattr(self, "_cost"): + self._cost = self.get_total_cost() + return self._cost + + @property + def avg_cost(self): + if not hasattr(self, "_avg_cost"): + self._avg_cost = self.cost / self.total_num + return self._avg_cost + + def get_total_cost(self): + """ + return the average cost of the data + """ + prompt_cost_perk, completion_cost_perk = PRICING_MODELS[self.pricing_model] + prompt_cost = self.prompt_tokens_num * prompt_cost_perk / 1000 + completion_cost = self.completion_tokens_num * completion_cost_perk / 1000 + total_cost = prompt_cost + completion_cost + return total_cost + + def get_eval2_ratio_each_problem(self): + """ + return the ratio of the second evaluation + """ + txt_out = "" + for i in self.data: + if self.Eval0_pass(i) and i.get("Eval1_pass",0): + task_id = i["task_id"] + eval2_ratio = i.get("Eval2_ratio", "No Eval2 ratio data") + txt_out += "%s: %s\n" % (task_id, eval2_ratio) + return txt_out + + def get_eval2b_ratio_each_problem(self): + """ + return the ratio of the second evaluation + """ + txt_out = "" + for i in self.data: + if self.Eval2b_exist: + if self.Eval0_pass(i) and i.get("Eval1_pass",0): + task_id = i["task_id"] + eval2_ratio = i.get("Eval2b_ratio", "No Eval2b ratio data") + txt_out += "%s: %s\n" % (task_id, eval2_ratio) + else: + txt_out = "No Eval2b data" + return txt_out + + def get_debug_infotable(self): + """ + return the debug info table: + | un-debugged | debugged | total | + failed | - | 2 | 2 | + Eval0 | 3 | 5 | 8 | + Eval1 | 2 | 2 | 4 | + Eval2 | 1 | 0 | 1 | + if have Eval2b: + Eval2b | 1 | 0 | 1 | + """ + txt_out = "" + # debugged but failed + failed_debugged_num = 0 + failed_undebugged_num = 0 + Eval0_debugged_num, Eval1_debugged_num, Eval2_debugged_num, Eval2b_debugged_num = 0, 0, 0, 0 + Eval0_undebugged_num, Eval1_undebugged_num, Eval2_undebugged_num, Eval2b_undebugged_num = 0, 0, 0, 0 + if self.reboot_times_exist or self.TB_corrected_exist: + mode = "funcdebug" + else: + mode = "syndebug" + for i in self.data: + if mode == "syndebug": + debugged = (self.debug_iter(i) != 0) + elif mode == "funcdebug": + debugged = (i.get("reboot_times", 0) > 0 or (i.get("TB_corrected", False))) + else: + raise ValueError("mode should be 'syndebug' or 'funcdebug'") + failed_debugged_num += 1 if not self.Eval0_pass(i) and debugged else 0 + failed_undebugged_num += 1 if not self.Eval0_pass(i) and (not debugged) else 0 + Eval0_debugged_num += 1 if self.Eval0_pass(i) and debugged else 0 + Eval0_undebugged_num += 1 if self.Eval0_pass(i) and (not debugged) else 0 + Eval1_debugged_num += 1 if self.Eval0_pass(i) and i.get("Eval1_pass", 0) and debugged else 0 + Eval1_undebugged_num += 1 if self.Eval0_pass(i) and i.get("Eval1_pass", 0) and (not debugged) else 0 + Eval2_debugged_num += 1 if self.Eval0_pass(i) and i.get("Eval1_pass", 0) and self.Eval2_pass(i) and debugged else 0 + Eval2_undebugged_num += 1 if self.Eval0_pass(i) and i.get("Eval1_pass", 0) and self.Eval2_pass(i) and (not debugged) else 0 + if self.Eval2b_exist: + Eval2b_debugged_num += 1 if self.Eval0_pass(i) and i.get("Eval1_pass", 0) and self.Eval2b_pass(i) and debugged else 0 + Eval2b_undebugged_num += 1 if self.Eval0_pass(i) and i.get("Eval1_pass", 0) and self.Eval2b_pass(i) and (not debugged) else 0 + failed_num = failed_debugged_num + failed_undebugged_num + Eval0_num = Eval0_debugged_num + Eval0_undebugged_num + Eval1_num = Eval1_debugged_num + Eval1_undebugged_num + Eval2_num = Eval2_debugged_num + Eval2_undebugged_num + if self.Eval2b_exist: + Eval2b_num = Eval2b_debugged_num + Eval2b_undebugged_num + # make a table; each cell should have a width of 11 + txt_out += ("SYNTACTIC" if mode == "syndebug" else "FUNCTIONAL") + " debug info table:\n" + txt_out += "(debugged here means " + ("syntactic debugging" if mode == "syndebug" else "functional debugging") + ")\n" + if mode == "syndebug": + txt_out += " | un-synt-debugged | synt-debugged | total |\n" + elif mode == "funcdebug": + txt_out += " | un-func-debugged | func-debugged | total |\n" + txt_out += "failed | %16d | %13d | %5d |\n" % (failed_undebugged_num, failed_debugged_num, failed_num) + txt_out += "Eval0 | %16d | %13d | %5d |\n" % (Eval0_undebugged_num, Eval0_debugged_num, Eval0_num) + txt_out += "Eval1 | %16d | %13d | %5d |\n" % (Eval1_undebugged_num, Eval1_debugged_num, Eval1_num) + txt_out += "Eval2 | %16d | %13d | %5d |\n" % (Eval2_undebugged_num, Eval2_debugged_num, Eval2_num) + if self.Eval2b_exist: + txt_out += "Eval2b | %16d | %13d | %5d |\n" % (Eval2b_undebugged_num, Eval2b_debugged_num, Eval2b_num) + return txt_out + + def get_iv_runing_time_info(self): + max_time = 0.0 + min_time = 0.0 + total_time = 0.0 + cnt = 0 + for i in self.data: + if self.Eval0_pass(i): + time = float(i.get("iv_runing_time", 0.0)) + if (time > max_time) or (max_time == 0.0): + max_time = time + if (time < min_time) or (min_time == 0.0): + min_time = time + total_time += time + cnt += 1 + avg_time = total_time / cnt if cnt != 0 else 0.0 + if cnt != 0: + self.out_txt += "\n#### iv_runing_time info:\n" + self.out_txt += "avg_time: %.2fs\n" % avg_time + self.out_txt += "max_time: %.2fs\n" % max_time + self.out_txt += "min_time: %.2fs\n" % min_time + + + def Eval0_pass(self, data): + if "Eval0_pass" in data.keys(): + return data["Eval0_pass"] # latest version + elif "sim_pass" in data.keys(): + return data["sim_pass"] # old version + else: + return False + + def Eval1_pass(self, data): + return data.get("Eval1_pass", False) + + def Eval2_pass(self, data): + """check if one data pass the Eval 2""" + # we use this to compensate special cases: m2014_q3 + if data["task_id"] == "m2014_q3": + if data.get("Eval2_failed_mutant_idxes", []) == [3,4,7,8,9,10] or self.loose_Eval2_pass(data): + return True + else: + return False + # normal cases + else: + return self.loose_Eval2_pass(data) + + def Eval0_scencheck_pass(self, data): + return self.Eval0_pass(data) and data.get("checklist_worked", False) + + def Eval1_scencheck_pass(self, data): + return self.Eval1_pass(data) and data.get("checklist_worked", False) + + def Eval2_scencheck_pass(self, data): + return self.Eval2_pass(data) and data.get("checklist_worked", False) + + def Eval0_noscencheck_pass(self, data): + return self.Eval0_pass(data) and (not data.get("checklist_worked", False)) + + def Eval1_noscencheck_pass(self, data): + return self.Eval1_pass(data) and (not data.get("checklist_worked", False)) + + def Eval2_noscencheck_pass(self, data): + return self.Eval2_pass(data) and (not data.get("checklist_worked", False)) + + def Eval0_nodebug_pass(self, data): + return (self.Eval0_pass(data)) and (self.debug_iter(data) == 0) + + def Eval1_nodebug_pass(self, data): + return (self.Eval1_pass(data)) and (self.debug_iter(data) == 0) + + def Eval2_nodebug_pass(self, data): + return (self.Eval2_pass(data)) and (self.debug_iter(data) == 0) + + def Eval2b_pass(self, data): + """check if one data pass the Eval 2""" + # we use this to compensate special cases: m2014_q3 + if data["task_id"] == "m2014_q3": + if self.Eval2_pass(data): + return True + else: + return False + # normal cases + else: + return self.loose_Eval2b_pass(data) + + def debug_iter(self, data): + if data.get("debug_iter", None) is not None: + return data["debug_iter"] + else: + return data.get("debug_iter_iv", 0) + data.get("debug_iter_py", 0) + + def loose_Eval2_pass(self, data): + """pass for 9/10, 8/10 and 4/5""" + if data.get("Eval2_pass", False): + return True + ratio_str = data.get("Eval2_ratio", None) + if ratio_str is None: + return False + numerator, denominator = ratio_str.split("/") + numerator, denominator = int(numerator), int(denominator) + # if int(numerator) + 1 >= int(denominator): + if float(numerator) / float(denominator) >= self.loose_factor: + return True + else: + return False + + def loose_Eval2b_pass(self, data): + """pass for 9/10, 8/10 and 4/5""" + if data.get("Eval2b_pass", False): + return True + ratio_str = data.get("Eval2b_ratio", None) + if ratio_str is None: + return False + numerator, denominator = ratio_str.split("/") + numerator, denominator = int(numerator), int(denominator) + # if int(numerator) + 1 >= int(denominator): + if float(numerator) / float(denominator) >= self.loose_factor: + return True + else: + return False + +class MultiAnalyzer(muti_dictlist): + def __init__(self, group_dir:str=None, pass_at_k_kvalues = K_LIST): + """ + group_dir: includes many subdirs, each subdir contains a Chatbench_RunInfo.json + """ + super().__init__(id_key="task_id") + self.runinfo_paths = [] + self.result = {} # include the final results + self.pass_at_k_kvalues = pass_at_k_kvalues + self.exclude_debug = False # this is uesd in baseline when analyzing the data without debug + self.messages = "" + self.group_dir = group_dir + for subdir in os.listdir(group_dir): + path_runinfo = os.path.join(group_dir, subdir, "Chatbench_RunInfo.json") + if os.path.exists(path_runinfo): + self.runinfo_paths.append(path_runinfo) + for path in self.runinfo_paths: + self.dictlists.append(Analyzer(ls.load_json_dict(path))) + self.dictlists: list[Analyzer] + # check if the values in self.num(list) are the same + if not self.all_equal("num"): + print(self.num) + raise ValueError("The total_num of the data are not the same") + + @property + def analyzers(self): + return self.dictlists + + def run(self, Evals=["Eval0", "Eval1", "Eval2"]): + num_tasks = self.dictlists[0].total_num + pass_at = self.pass_at_k_kvalues + for Eval_idx in Evals: + for pass_at_k in pass_at: + self.Evalx_ratio_passatk(Eval_idx, pass_at_k) + self.messages += "\n########## Analyze of Chatbench_RunInfos ##########\n" + self.messages += "\n#### basic info:\n" + self.messages += "total number of tasks: %d\n" % self.dictlists[0].total_num + self.messages += "sample numbers: %d\n" % len(self.dictlists) + self.messages += "\n#### pass@k ratios:\n" + for key, value in self.result.items(): + self.messages += "%s: %.2f%% (%.1f)\n" % (key, value*100, value*num_tasks) + self.messages += "\nloose Eval2 pass metric applied: %s\n\n" % self.dictlists[0].loose_factor + + def save(self, path:str=None): + if path is None: + path = DEFAULT_LOG_PATH + with open(path, "w") as f: + f.write(self.messages) + # also, write the current time + f.write("analysis time: %s\n" % (utils.get_time())) + + def get_avg_tokens_one_task(self): + self.prompt_tokens_num = 0 + self.completion_tokens_num = 0 + for analyzer in self.dictlists: + self.prompt_tokens_num += analyzer.prompt_tokens_num + self.completion_tokens_num += analyzer.completion_tokens_num + self.avg_prompt_tokens = self.prompt_tokens_num / len(self.dictlists) / self.dictlists[0].total_num + self.avg_completion_tokens = self.completion_tokens_num / len(self.dictlists) / self.dictlists[0].total_num + self.messages += "average prompt tokens: %d\n" % self.avg_prompt_tokens + self.messages += "average completion tokens: %d\n" % self.avg_completion_tokens + + def get_avg_pass_by_disc_and_corr(self): + self.pass_by_corrected = 0.0 + self.pass_by_disc = 0.0 + for analyzer in self.dictlists: + self.pass_by_corrected += analyzer.corrected_num + self.pass_by_disc += analyzer.autoline_reboot_task_num + self.pass_by_corrected /= len(self.dictlists) + self.pass_by_disc /= len(self.dictlists) + self.messages += "passed with functional corrector: %.1f\n" % self.pass_by_corrected + self.messages += "passed with autoline reboot action: %.1f\n" % self.pass_by_disc + + def renew_result_dict(self): + self.result_dict = HDLBitsProbset() + self.result_dict.create_empty_set_via_taskids(self.dictlists[0].task_id_list) + + def Evalx_ratio_passatk(self, Eval_idx="Eval0", pass_at:int=1): + """ + return the ratio of the Eval0 pass under pass@k + """ + # assert Eval_idx in ["Eval0", "Eval1", "Eval2", "Eval2b"], "Eval_idx should be one of Eval0, Eval1, Eval2, Eval2b" + if not hasattr(Analyzer, Eval_idx + "_pass"): + raise ValueError("The function %s_pass is not defined in Analyzer" % Eval_idx) + k = pass_at + n = len(self.dictlists) + Evalx_pass_at_k_total = 0 + # compute the pass ratio under pass@k for each task + for task_id in self.dictlists[0].task_id_list: + if hasattr(self, "result_dict"): + task_result = self.result_dict.access_data_via_taskid(task_id) + pass_num = 0 + for dictlist in self.dictlists: + Evalx_pass_func = getattr(dictlist, "%s_pass"%Eval_idx) + # if dictlist.access_data_via_taskid(task_id)["%s_pass"%Eval_idx]: + if Evalx_pass_func(dictlist.access_data_via_taskid(task_id)): + if not (self.exclude_debug and dictlist.debug_iter(dictlist.access_data_via_taskid(task_id))): + # if exclude_debug is True and the task is debugged, we will not count it + pass_num += 1 + # data = self.result_set.access_data_via_taskid(task_id) + pass_at_k = self.pass_at_k_under_n(n, k, pass_num) + Evalx_pass_at_k_total += pass_at_k + if hasattr(self, "result_dict"): + task_result["%s_pass_num"%Eval_idx] = pass_num + task_result["%s_pass_at_%d"%(Eval_idx, k)] = pass_at_k + Evalx_pass_at_k_total /= self.dictlists[0].total_num + self.result["%s_pass_at_%d" % (Eval_idx, k)] = Evalx_pass_at_k_total + + + @staticmethod + def pass_at_k_under_n(n:int, k:int, c:int): + """ + - n: total number of samples + - k: number of samples we want to pick + - c: number of samples passed + - output: pass@k under n + - return the pass ratio under pass@k for n times; we have n samples, pass_num samples passed. Now we want to calculate the possibility that we pick k samples and at least one of them passed + """ + return 1 - (math.comb(n-c, k) / math.comb(n, k)) + +def Eval2_histogram(): + """draw Eval2 histogram""" + k_list = [1,5,10] + multi_analyzer = MultiAnalyzer(MULTI_DIR, k_list) + ratios = [] + for analyzer in multi_analyzer.dictlists: + for i in analyzer.data: + if analyzer.Eval0_pass(i) and analyzer.Eval1_pass(i): + # get the numerical ratio + ratio_str = i.get("Eval2_ratio", None) + if ratio_str is None: + continue + numerator, denominator = ratio_str.split("/") + ratio = float(numerator) / float(denominator) + ratios.append(ratio) + # draw histogram of ratios, it will have 10 bars, 0~10%, 10~20%, ..., 90~100% + import matplotlib.pyplot as plt + import matplotlib + matplotlib.use('Agg') + plt.hist(ratios, bins=10, range=(0,1)) + # title: distribution of Eval2 (Our work) + plt.title("distribution of Eval2 (Baseline)") + # y label is the number of tasks, tick is 10 + plt.ylabel("number of tasks") + plt.yticks(range(0, 700, 50)) + # small grid so we can see the number of tasks, major grid, both axis. the grid color should be very light + plt.grid(True, which='both', axis='both', color='lightgray', linestyle='-', linewidth=0.5) + # x label is the ratio, tick is 10% + plt.xlabel("ratio") + plt.xticks([0.1 * i for i in range(11)]) + # save to analysis/eval2_histogram.png + plt.savefig(os.path.join(DEFAULT_SAVING_DIR, "eval2_histogram_Baseline.png")) + # save the ratios to out, it is grouped by 0.1. the first group will contain both 0 and 0.1 + # the format is csv + with open(os.path.join(DEFAULT_SAVING_DIR, "eval2_ratios_Baseline.txt"), "w") as f: + for i in range(11): + ratio = 0.1 * i + ratio_num = len([j for j in ratios if math.floor(j*10) == i]) + f.write("%.1f, %d\n" % (ratio, ratio_num)) + # export the original bin data + with open(os.path.join(DEFAULT_SAVING_DIR, "eval2_ratios_bin_Baseline.txt"), "w") as f: + for i in ratios: + f.write("%.2f\n" % i) + plt.close() + +def task_eval2passtimes_analyze(): + multi_analyzer = MultiAnalyzer(MULTI_DIR) + pass_taskids_list = [] + for analyzer in multi_analyzer.dictlists: + # find the task_id that pass Eval2 + pass_taskids = [] + for data in analyzer.data: + if analyzer.Eval2_pass(data): + pass_taskids.append(data["task_id"]) + pass_taskids_list.append(pass_taskids) + # find the idx of task "lemming3" + idxs = [] + for idx, pass_taskids in enumerate(pass_taskids_list): + if "countbcd" in pass_taskids: + idxs.append(idx) + print("countbcd passed at:") + print(idxs) + # calculate the pass times for each task_id + # pass_times_dict = {} + # for pass_taskids in pass_taskids_list: + # for task_id in pass_taskids: + # if task_id in pass_times_dict.keys(): + # pass_times_dict[task_id] += 1 + # else: + # pass_times_dict[task_id] = 1 + # circuit_type_data_path = "data/HDLBits/HDLBits_circuit_type.jsonl" + # SEQ_task_ids = HDLBitsProbset(circuit_type_data_path, filter_content={"circuit_type": "SEQ"}).task_id_list + # # remove tasks that are not seq and then print + # seq_passed_tasks = [] + # for task_id in pass_times_dict.keys(): + # if task_id in SEQ_task_ids: + # seq_passed_tasks.append(task_id) + # # pick 5 most complex tasks according to complexity = 1*len(description) + 2*len(module_code) + # HDLdata = HDLBitsProbset("data/HDLBits/HDLBits_data.jsonl", only_tasks=seq_passed_tasks) + # HDLdata.data.sort(key=lambda x: 1*len(x["description"]) + 2*len(x["module_code"]), reverse=True) + # # print the task_id and pass times of the 5 most complex tasks + # for i in range(10): + # task_id = HDLdata.data[i]["task_id"] + # pass_times = pass_times_dict[task_id] + # print(task_id + ": " + str(pass_times)) + +if __name__ == "__main__": + main() +# FULLEXP_no1_paths = [ +# "saves/1211~1217/Chatbench_RunInfo.json" +# ] + + +# def get_TCpass_num(data): +# """ +# return the number of passed tasks +# """ +# pass_num = 0 +# for i in data: +# if i["TC_pass"]: +# pass_num += 1 +# return pass_num + +# def get_debugTCpass_num(data): +# """ +# return the number of passed tasks with debug iter != 0 +# """ +# pass_num = 0 +# for i in data: +# if i["TC_pass"] and self.debug_iter(i) != 0: +# pass_num += 1 +# return pass_num + +# def get_debugsimpass_num(data): +# """ +# return the number of passed tasks with debug iter != 0 +# """ +# pass_num = 0 +# for i in data: +# if self.Eval0_pass(i) and self.debug_iter(i) != 0: +# pass_num += 1 +# return pass_num + +# def get_average_debugiter_debugTCpass(data): +# """ +# return the average debug iter of the passed with debug data +# """ +# debug_iter_sum = 0 +# debug_iter_num = 0 +# for i in data: +# if i["TC_pass"] and self.debug_iter(i) != 0: +# debug_iter_sum += self.debug_iter(i) +# debug_iter_num += 1 +# return debug_iter_sum / debug_iter_num + +# def get_average_time(data): +# """ +# return the average time of the data +# """ +# time_sum = 0 +# for i in data: +# time_sum += i["time"] +# return time_sum / len(data) + +# def get_num_of_onetime_simpass(data): +# """ +# return the number of tasks that passed in the first run +# """ +# pass_num = 0 +# for i in data: +# if self.Eval0_pass(i) and self.debug_iter(i) == 0: +# pass_num += 1 +# return pass_num + + +# def analyze_EXP1_main(json_file_list): +# output_data_list = [] +# for json_file in json_file_list: +# data = ls.load_json_dict(json_file) +# output_data_list.extend(data) +# analyze(output_data_list) + +# def correct_exp_no1_main(json_file_list): +# output_data_list = correct_exp_no1(json_file_list) +# analyze(output_data_list) + +# def analyze(output_data_list): +# # show all of the above processed data +# print("total number of tasks: %d" % (get_total_num(output_data_list))) +# print("number of simpassed tasks: %d" % (get_simpass_num(output_data_list))) +# print("number of allpassed tasks: %d" % (get_TCpass_num(output_data_list))) +# print("TCpass percentage: %.2f%%" % (get_TCpass_num(output_data_list) / get_total_num(output_data_list) * 100)) +# print("number of debug_and_TCpass: %d" % (get_debugTCpass_num(output_data_list))) +# print("number of debug_and_simpass: %d" % (get_debugsimpass_num(output_data_list))) +# print("average debug iter of debug_and_TCpass: %.2f" % (get_average_debugiter_debugTCpass(output_data_list))) +# print("average time: %.2fs" % (get_average_time(output_data_list))) +# # save them to a txt file analyze_out.txt +# with open("analyze_out.txt", "w") as f: +# f.write("total number of tasks: %d\n" % (get_total_num(output_data_list))) +# f.write("number of simpassed tasks: %d\n" % (get_simpass_num(output_data_list))) +# f.write("number of allpassed tasks: %d\n" % (get_TCpass_num(output_data_list))) +# f.write("TCpass percentage: %.2f%%\n" % (get_TCpass_num(output_data_list) / get_total_num(output_data_list) * 100)) +# f.write("number of debug_and_TCpass: %d\n" % (get_debugTCpass_num(output_data_list))) +# f.write("number of debug_and_simpass: %d\n" % (get_debugsimpass_num(output_data_list))) +# f.write("average debug iter of debug_and_TCpass: %.2f\n" % (get_average_debugiter_debugTCpass(output_data_list))) +# f.write("average time: %.2fs\n" % (get_average_time(output_data_list))) +# # also, write the current time +# f.write("time: %s\n" % (time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()))) + +# def correct_exp_no1(json_file_list): +# new_results = [] +# for json_file in json_file_list: +# data_dir = json_file[:json_file.rfind("/")+1] +# results = ls.load_json_dict(json_file) +# for prob_result in results: +# prob_dir = data_dir + prob_result["task_id"] + "/" +# if prob_result["debug_iter"] == 0: +# prob_last_run_info_path = prob_dir + "TBgen_codes/" + "run_info.txt" +# else: +# prob_last_run_info_path = prob_dir + "debug_%s" % (prob_result["debug_iter"]) + "/" + "run_info.txt" +# prob_last_run_info = ls.load_txt(prob_last_run_info_path) +# # check if "All test cases passed" is in the last run info +# if "test cases passed" in prob_last_run_info: +# prob_result["TC_pass"] = True +# else: +# prob_result["TC_pass"] = False +# new_results.extend(results) +# ls.save_dict_json_form(new_results, "corrected_exp_no1.json") +# return new_results + +# if __name__ == "__main__": +# # correct_exp_no1_main(FULLEXP_no1_paths) +# analyze_EXP1_main(FULLEXP_no1_paths) +# # main() \ No newline at end of file diff --git a/autoline/TB1_gen.py b/autoline/TB1_gen.py new file mode 100644 index 0000000..eb32793 --- /dev/null +++ b/autoline/TB1_gen.py @@ -0,0 +1,43 @@ +""" +Description : The TB generation stage in the autoline. The main TB generation workflow is implemented in prompt_scriptws +Author : Ruidi Qiu (r.qiu@tum.de) +Time : 2024/7/24 11:27:21 +LastEdited : 2024/8/12 23:30:30 +""" + + +from prompt_scripts import get_script, BaseScript +from loader_saver import log_localprefix + +class TaskTBgen(): + # TODO: in the future use pythonized prompt scripts and this class to replace the old TaskTBgen + """TBgen, in this class we generate tb by calling different python script according to stage_template""" + def __init__(self, prob_data: dict, TBgen_prompt_script: str, task_dir: str, config): + self.prob_data = prob_data + self.prompt_script_name = TBgen_prompt_script + self.task_dir = task_dir + self.config = config + WorkFlowClass = get_script(TBgen_prompt_script) + self.workflow = WorkFlowClass( + prob_data = prob_data, + task_dir = task_dir, + config = config + ) + + @log_localprefix("TBgen") + def run(self): + self.workflow() + + @property + def scenario_num(self): + return self.get_wf_attr("scenario_num") + + @property + def scenario_dict(self): + return self.get_wf_attr("scenario_dict") + + def get_wf_attr(self, attr_name:str): + if hasattr(self.workflow, attr_name): + return getattr(self.workflow, attr_name) + else: + return None \ No newline at end of file diff --git a/autoline/TB2_syncheck.py b/autoline/TB2_syncheck.py new file mode 100644 index 0000000..0a20f94 --- /dev/null +++ b/autoline/TB2_syncheck.py @@ -0,0 +1,416 @@ +""" +Description : This is the TB syntactic checking stage in the autoline (previously named as TaskTBsim in autoline.py) +Author : Ruidi Qiu (r.qiu@tum.de) +Time : 2024/7/24 11:24:31 +LastEdited : 2024/8/23 15:53:15 +""" + +import os +import LLM_call as llm +import iverilog_call as iv +import python_call as py +import loader_saver as ls +from config import Config +from loader_saver import autologger as logger +from loader_saver import log_localprefix +from prompt_scripts import get_script, BaseScript +from utils.utils import Timer, get_time + +IDENTIFIER = { + "tb_start" : "```verilog", + "tb_end" : "```" +} + +TESTBENCH_TEMPLATE = """%s +`timescale 1ns / 1ps +(more verilog testbench code here...) +endmodule +%s""" % (IDENTIFIER["tb_start"], IDENTIFIER["tb_end"]) + +DEBUG_TEMPLATE = """please fix the verilog testbench code below according to the error message below. please directly give me the corrected verilog testbench codes. +Attention: never remove the irrelevant codes!!! +your verilog testbench should be like: +%s +please only reply the full code modified. NEVER remove other irrelevant codes!!! +The testbench I give you is the one with error. To be convienient, each of the line begins with a line number. The line number also appears at the error message. You should use the line number to locate the error with the help of error message. +""" % (TESTBENCH_TEMPLATE) + +DEBUG_FINAL_INSTR = """ please directly give me the corrected verilog codes, no other words needed. Your verilog codes should start with [```verilog] and end with [```].""" + +DEBUG_TEMPLATE_PY = """please fix the python code below according to the error message below. please directly give me the corrected python codes. +Attention: never remove the irrelevant codes!!! +please only reply the full code modified. NEVER remove other irrelevant codes!!! +The python code I give you is the one with error. To be convienient, each of the line begins with a line number. The line number also appears at the error message. You should use the line number to locate the error with the help of error message. +""" + +DEBUG_FINAL_INSTR_PY = """ please directly give me the corrected python codes, no other words needed. Your python codes should start with [```python] and end with [```].""" + +# will be discarded by 15/08/2024 +# DEBUG_TEMPLATE_END = """ +# VERY IMPORTANT: please ONLY reply the full code modified. NEVER remove other irrelevant codes!!! +# Your testbench SHOULD NOT have the line number at the beginning of each line!!! +# """ + +class TaskTBsim(): + """ + #### input: + - ivcode_path: + - the path of iverilog dir (xxx/TB_gen/), will contain all verilog files. generated .vvp will also be saved here + #### output: + - dict of the simulation result + - "sim_pass" : bool (whether the simulation is successful. This is only the necessary condition of the correctness of the testbench) + - "debug_iter" : int (the number of debug iterations) + - "sim_out" : str (the output of the simulation) + - "sim_err" : str (the error of the simulation) + - "TB_gen_debugged" : str or None (the testbench code after debug) + #### iverilog_path: + - the path of iverilog dir, will contain all verilog files. generated .vvp will also be saved here + #### task_id: + - the name of the problem, will be used as the name of generated files + file structure: + - original + - task_id.v + - task_id_tb.v + - task_id_vlist.txt + - task_id.vvp + - debug_1 + - task_id.v + - task_id_tb.v + - task_id_vlist.txt + - task_id.vvp + - debug_2 + - ... + """ + def __init__(self, TBgen: BaseScript, TB_code: str, module_header: str, task_dir: str, task_id: str, config): + self.TBgen = TBgen + self.TB_code_now = TB_code + self.module_header = module_header + self.task_dir = task_dir if task_dir.endswith("/") else task_dir + "/" # for the compatibility with the old version + self.task_id = task_id + self.config = config + self.working_dir = TBgen.TB_code_dir if TBgen.TB_code_dir.endswith("/") else TBgen.TB_code_dir + "/" # will change during the debug process + self.DUT_code = module_header + "\n\nendmodule\n" + self.debug_iter_max = config.autoline.debug.max + self.debug_iter_to_reboot = config.autoline.debug.reboot + self.proc_timeout = config.autoline.timeout + # self.debug_iter_now = 0 # this is a counter for both iverilog and python so it is possible to be larger than debug_iter_max + self.debug_iter_iv_now = 0 + self.debug_iter_after_reboot_iv = 0 + self.debug_iter_py_now = 0 + self.debug_iter_after_reboot_py = 0 + self.reboot_both = False + # self.debug_iter_after_reboot = 0 + # pychecker related + self.pychecker_en = self.TBgen.Pychecker_en + self.PY_code_now = "" + if self.pychecker_en: + self.TBout_content = "" # will get after the last iverilog run + self.PY_code_now = self.TBgen.Pychecker_code + self.py_fail_reboot_both_iter = config.autoline.debug.py_rollback # will reboot both iv and py if python simulation failed xxx times + self.py_debug_focus = self.TBgen.py_debug_focus + # infos + self.sim_pass = False # this should be com_pass, but it is too late to change it now + self.py_pass = False + self.Eval0_pass = False + self.iverilog_info = None + self.reboot_both_times = 0 + self.iv_runing_time = 0.0 # the time of running the last iverilog + self.py_runing_time = 0.0 # the time of running the last python + self.tokens = {"prompt": 0, "completion": 0} + + @log_localprefix("TBsim") + def run(self): + if not self.pychecker_en: + self.run_iverilog() + self.Eval0_pass = self.sim_pass + else: + exit_en = False + while (not exit_en): + self.run_iverilog() + if self.sim_pass: + self.run_python() + # if (self.sim_pass and self.py_pass) or self.exceed_max_debug: + if not self.reboot_both: + exit_en = True + else: + exit_en = True + self.Eval0_pass = False + raise ValueError("TBsim: iverilog failed, python simulation is not allowed.") + self.Eval0_pass = self.sim_pass and self.py_pass + logger.info("TBsim finished : %s!"%(self.Eval0_pass)) + + def run_iverilog(self): + """ + - the main function of TBsim + """ + if not self.reboot_both: + # this will only be called at the first time of runing run_iverilog + self._save_code_run_iverilog() + self.sim_pass = self.iverilog_info[0] + while (self.debug_iter_iv_now < self.debug_iter_max) and (not self.sim_pass): + self.debug_iter_iv_now += 1 + if self.debug_iter_after_reboot_iv < self.debug_iter_to_reboot: + self.debug_iter_after_reboot_iv += 1 + self._debug_iv() + else: + self._reboot_iv() + self.sim_pass = self.iverilog_info[0] + self.reboot_both = False + if self.reboot_both: + # this means didn't enter the while, because debug_iter_max is already reached + logger.info("iverilog compilation (reboot from python) : failed! iverilog exceeded max debug iteration (%s)"%(self.debug_iter_max)) + if self.sim_pass: + logger.info("iverilog compilation : passed!") + else: + logger.info("iverilog compilation : failed! exceeded max debug iteration (%s)"%(self.debug_iter_max)) + # self.sim_out = self.iverilog_info[4]["out"] if self.iverilog_info[4] is not None else "" + # self.sim_err = self.iverilog_info[-1] + # clean .vcd wave files + self.clean_vcd() + + def run_python(self): + # read the TBout.txt into TBout_content in working_dir + with open(self.TBout_path, "r") as f: + self.TBout_content = f.read() + self.debug_iter_after_reboot_py = 0 + py_rollback = 0 # local variable + self._save_code_run_python() + # self.debug_iter_py_now + while (self.debug_iter_py_now < self.debug_iter_max) and (not self.python_info[0]): + if (not self.python_info[0]) and (py_rollback >= self.py_fail_reboot_both_iter): + # +1: debug py fail + [generated py fail] + self.reboot_both = True + break + py_rollback += 1 + self.debug_iter_py_now += 1 + if self.debug_iter_after_reboot_py < self.debug_iter_to_reboot: + self.debug_iter_after_reboot_py += 1 + self._debug_py() + else: + self._reboot_py() + # self._reboot_py() # only reboot, no debugging because python debugging is much harder than verilog + # currently debug_py doesn't support reboot + if self.reboot_both: + self.py_pass = False + self.sim_pass = False + self.debug_iter_after_reboot_iv = self.debug_iter_to_reboot + logger.info("python simulation : failed! will reboot both iverilog and python") + elif self.python_info[0]: + self.py_pass = True + logger.info("python simulation : passed!") + else: + self.py_pass = False + logger.info("python simulation : failed! exceeded max debug iteration (%s)"%(self.debug_iter_max)) + self.py_out = self.python_info[1]["out"] if self.python_info[1] is not None else "" + self.py_err = self.python_info[-1] + + def _debug_iv(self): + with Timer(print_en=False) as debug_time: + logger.info("iverilog simulation failed! Debuging... (debug_iter = %s)"%(self.debug_iter_iv_now)) + self.working_dir = self.task_dir + "debug_%s/" % (self.total_debug_iter_now) + os.makedirs(self.working_dir, exist_ok=True) + debug_prompt = self._debug_prompt_gen_iv() + debug_message = [{"role": "user", "content": debug_prompt}] + gpt_response, info = llm.llm_call(debug_message, self.config.gpt.model, self.config.gpt.key_path) + debug_message = info["messages"] + self.TB_code_now = llm.extract_code(gpt_response, "verilog")[-1] + self.TB_code_now = self.del_linemark(self.TB_code_now) + self._save_code_run_iverilog() + logger.info("%s: verilog DEBUG finished (%ss used)" % (self.debug_iter_info("iv"), round(debug_time.interval, 2))) + self.tokens["prompt"] += info["usage"]["prompt_tokens"] + self.tokens["completion"] += info["usage"]["completion_tokens"] + ls.save_messages_to_txt(debug_message, self.working_dir+"debug_messages.txt") + + def _reboot_iv(self): + # change TBgen's code dir + with Timer (print_en=False) as reboot_time: + logger.info("iverilog simulation failed! Rebooting... (debug_iter = %s)"%(self.debug_iter_iv_now)) + self.working_dir = self.task_dir + "debug_%s_reboot/" % (self.total_debug_iter_now) + os.makedirs(self.working_dir, exist_ok=True) + self.TBgen.run_reboot(self.working_dir, reboot_mode="TB") + self.TB_code_now = self.TBgen.TB_code + self._save_code_run_iverilog() + logger.info("%s: verilog REBOOT finished (%ss used)" % (self.debug_iter_info("iv"), round(reboot_time.interval, 2))) + # the tookens will be added into TBgen's tokens count, we don't count it again here. + # reset reboot counter + self.debug_iter_after_reboot_iv = 0 + + def _debug_py(self): + with Timer(print_en=False) as debug_time: + logger.info("python compilation failed! Debuging python... (debug_iter = %s)"%(self.debug_iter_py_now)) + self.working_dir = self.task_dir + "debug_%s/" % (self.total_debug_iter_now) + os.makedirs(self.working_dir, exist_ok=True) + # run gpt + debug_prompt = self._debug_prompt_gen_py() + debug_message = [{"role": "user", "content": debug_prompt}] + gpt_response, info = llm.llm_call(debug_message, self.config.gpt.model, self.config.gpt.key_path) + debug_message = info["messages"] + self.PY_code_now = llm.extract_code(gpt_response, "python")[-1] + self.PY_code_now = self.del_linemark(self.PY_code_now) + if self.py_debug_focus: # currently only support pychecker SEQ mode + self.PY_code_now = self._py_focus(self.PY_code_now, before=False) + self._save_code_run_python() + logger.info("%s: python DEBUG finished (%ss used)" % (self.debug_iter_info("py"), round(debug_time.interval, 2))) + self.tokens["prompt"] += info["usage"]["prompt_tokens"] + self.tokens["completion"] += info["usage"]["completion_tokens"] + ls.save_messages_to_txt(debug_message, self.working_dir+"debug_messages.txt") + + def _reboot_py(self): + # change TBgen's code dir + with Timer (print_en=False) as reboot_time: + logger.info("python compilation failed! Rebooting... (debug_iter = %s)"%(self.debug_iter_py_now)) + self.working_dir = self.task_dir + "debug_%s_reboot/" % (self.total_debug_iter_now) + os.makedirs(self.working_dir, exist_ok=True) + self.TBgen.run_reboot(self.working_dir, reboot_mode="PY") + self.PY_code_now = self.TBgen.Pychecker_code + self._save_code_run_python() + logger.info("%s: python REBOOT finished (%ss used)" % (self.debug_iter_info("py"), round(reboot_time.interval, 2))) + # the tookens will be added into TBgen's tokens count, we don't count it again here. + # reset reboot counter + self.debug_iter_after_reboot_py = 0 + + def _save_code_run_iverilog(self): + with open(self.TB_path, "w") as f: + f.write(self.TB_code_now) + with open(self.DUT_path, "w") as f: + f.write(self.DUT_code) + with Timer(print_en=False) as iverilog_time: + self.iverilog_info = iv.iverilog_call_and_save(self.working_dir, silent=True, timeout=self.proc_timeout) + self.iv_runing_time = round(iverilog_time.interval, 2) + self.error_message_now = self.iverilog_info[-1] + if "program is timeout" in self.error_message_now: + # if the error message is timeout, we will delete the TBout.txt + # this is to avoid the situation that infinite loop produces a large TBout.txt + if os.path.exists(self.TBout_path): + os.remove(self.TBout_path) + self.clean_vvp() + + def _save_code_run_python(self): + with open(self.PY_path, "w") as f: + f.write(self.PY_code_now) + with open(self.TBout_path, "w") as f: + f.write(self.TBout_content) + with Timer(print_en=False) as python_time: + self.python_info = py.python_call_and_save(pypath=self.PY_path, silent=True, timeout=self.proc_timeout) + self.py_runing_time = round(python_time.interval, 2) + self.error_message_now = self.python_info[-1] + + def _debug_prompt_gen_iv(self): + debug_prompt = DEBUG_TEMPLATE + "\n previous testbench with error:\n" + self.add_linemark(self.TB_code_now) + "\n compiling error message:\n" + self.error_message_now + return debug_prompt + + def _debug_prompt_gen_py(self): + if self.py_debug_focus: + py_code = self._py_focus(self.PY_code_now, before=True) + else: + py_code = self.PY_code_now + if not ("program is timeout" in self.error_message_now): + self.error_message_now = self._py_error_message_simplify(self.error_message_now) + debug_prompt = DEBUG_TEMPLATE_PY + "\n previous python code with error:\n" + self.add_linemark(py_code) + "\n compiling error message:\n" + self.error_message_now + DEBUG_FINAL_INSTR_PY + return debug_prompt + + def _py_focus(self, code:str, before:bool): + """ + - code: the code under debug / after debug + - before: True, if before debug, will split the code; False, if after debug, will restore the code + """ + # KEY_WORD = "\ndef check_dut" + KEY_WORDs_1 = "def check_dut(vectors_in):\n golden_dut = GoldenDUT()\n failed_scenarios = []" + KEY_WORDs_2 = "\ndef SignalTxt_to_dictlist" + if before: + key_words = KEY_WORDs_1 if KEY_WORDs_1 in code else KEY_WORDs_2 + if key_words not in code: + py_code_focus = code + self.py_code_nofocus = "" + else: + py_code_focus = code.split(key_words)[0] + self.py_code_nofocus = key_words + code.split(key_words)[1] + return py_code_focus + else: + return code + self.py_code_nofocus + + @staticmethod + def _py_error_message_simplify(error_message:str, error_depth:int=1): + """ + - extract the key point of python error message + - error_depth: how many (how deep, from bottom to top) error messages to extract + """ + msg_lines = error_message.split("\n") + msg_out = "" + for line in reversed(msg_lines): + msg_out = line + "\n" + msg_out + if "File" in line: + error_depth -= 1 + if error_depth == 0: + break + return msg_out + + @property + def exceed_max_debug(self): + return (self.debug_iter_iv_now >= self.debug_iter_max) or (self.debug_iter_py_now >= self.debug_iter_max) + + @property + def total_debug_iter_now(self): + return self.debug_iter_iv_now + self.debug_iter_py_now + + @property + def TB_path(self): + return self.working_dir + self.task_id + "_tb.v" + + @property + def DUT_path(self): + return self.working_dir + self.task_id + ".v" + + @property + def PY_path(self): + return self.working_dir + self.task_id + "_tb.py" + + @property + def TBout_path(self): + return self.working_dir + "TBout.txt" + + def debug_iter_info(self, type): + """return debug iter info string. Type: "iv" or "py" """ + if self.pychecker_en: + if type == "iv": + return "verilog iter - %d/%d, total - %d/%d"%(self.debug_iter_iv_now, self.debug_iter_max, self.total_debug_iter_now, self.debug_iter_max*2) + elif type == "py": + return "python tier - %d/%d, total - %d/%d"%(self.debug_iter_py_now, self.debug_iter_max, self.total_debug_iter_now, self.debug_iter_max*2) + else: + raise ValueError("TaskTBsim.debug_iter_info(type): type should be 'iv' or 'py'") + else: + # only iverilog + return "debug iter %d/%d"%(self.debug_iter_iv_now, self.debug_iter_max) + + @staticmethod + def add_linemark(code: str): + """add the line mark (1., 2., ...) to the code at the beginning of each line""" + code = code.split("\n") + code = [str(i+1) + ". " + line for i, line in enumerate(code)] + return "\n".join(code) + + @staticmethod + def del_linemark(code: str): + """delete the line mark at the begening of each line if line mark exists""" + code = code.split("\n") + if code[1].split(".")[0].isdigit(): # use code[1] in case the first line is empty + code = [line.split(". ")[1:] for line in code] + for i, line in enumerate(code): + code[i] = ". ".join(line) + return "\n".join(code) + + def clean_vcd(self): + """clean the .vcd files in the task_dir""" + clean_dir = self.task_dir[:-1] if self.task_dir.endswith("/") else self.task_dir + for root, dirs, files in os.walk(clean_dir): + for file in files: + if file.endswith(".vcd"): + os.remove(os.path.join(root, file)) + + def clean_vvp(self): + """clean the .vvp files in the task_dir""" + clean_dir = self.task_dir[:-1] if self.task_dir.endswith("/") else self.task_dir + for root, dirs, files in os.walk(clean_dir): + for file in files: + if file.endswith(".vvp"): + os.remove(os.path.join(root, file)) \ No newline at end of file diff --git a/autoline/TB3_funccheck.py b/autoline/TB3_funccheck.py new file mode 100644 index 0000000..0514cca --- /dev/null +++ b/autoline/TB3_funccheck.py @@ -0,0 +1,836 @@ +""" +Description : The functionality checking of the generated TB, the submodule of Autoline +Author : Ruidi Qiu (r.qiu@tum.de) +Time : 2024/7/22 10:36:06 +LastEdited : 2025/2/25 22:11:13 +""" + +import os +import LLM_call as llm +import iverilog_call as iv +import python_call as py +import numpy as np +import loader_saver as ls +import matplotlib.pyplot as plt +import matplotlib.colors as mcolors +from loader_saver import autologger as logger +from loader_saver import log_localprefix + + + +class TaskTBcheck(): + """ + ### description + - this is the self-checking stage of our pipeline; This is the main contribution of AutoBench2。 + - This stage is to check the functional correctness of the testbench generated by AutoBench. + """ + + def __init__(self, task_dir:str, task_id:str, description:str, module_header:str, TB_code_v:str, TB_code_py:str|None=None, rtl_list:list[str]=None, rtl_num:int=20, scenario_num=None, correct_max:int=3, runfiles_save:bool=True, discriminator_mode:str="col_full_wrong", corrector_mode:str="naive", circuit_type:str=None, rtl_compens_max_iter:int=3, rtl_compens_en:bool=True, desc_improve:bool=False, **LLM_kwargs) -> None: + """ + - input: + - task_dir: the root directory of the taskTBcheck + - task_id: the name of the problem + - description: the description of the problem + - module_header: the header of the module + - TB_code_v: the generated verilog testbench code + - TB_code_py (opt.): the generated python testbench code, if None, the tb is in a pure verilog mode + - rtl_list (opt.): the list of llm-generated RTL codes, if None, will generate 20 RTL codes using LLM + - rtl_num (opt.): the number of RTL codes to generate, only used when rtl_list is None + - scenario_num (opt.): the number of scenarios in the testbench, if None, will be calculated from the failed scenarios (not accurate but won't impact the results) + - correct_max (opt.): the maximum number of correction iterations + - runfiles_save (opt.): whether to save the compilation files in TB_discrimination + - discriminator_mode (default: col_full_wrong): the mode of the discriminator + - corrector_mode (default: naive): the mode of the corrector + - circuit_type (opt.): the type of the circuit, used in the corrector (better performance if provided) + - rtl_compens_max_iter (default: 3): the maximum number of iterations of RTL compensation + - rtl_compens_en (default: True): whether to enable RTL compensation + - **LLM_kwargs: the keyword arguments for LLM (used in corrector and rtl generation), including: + - "main_model": the main llm name used in TB_generation and correction + - "rtlgen_model": the llm naem used in RTL generation + - "api_key_path": the path of the api key + - "temperature": the temperature of LLM + """ + self.task_dir = task_dir + self.working_dir = self.task_dir + self.task_id = task_id + self.description = description + self.module_header = module_header + self.TB_code_v = TB_code_v + self.TB_code_py = TB_code_py + self.pychecker_en = TB_code_py is not None + self.rtl_list = rtl_list + self.scenario_num = scenario_num + self.correct_max = correct_max + self.runfiles_save = runfiles_save + self.main_model = LLM_kwargs.get("main_model", None) + self.rtlgen_model = LLM_kwargs.get("rtlgen_model", None) + self.llm_api_key_path = LLM_kwargs.get("api_key_path", "config/key_API.json") + self.llm_temperature = LLM_kwargs.get("temperature", None) + self.circuit_type = circuit_type + self.rtl_compens_max_iter = rtl_compens_max_iter # see self.discriminate_TB for more info + self.rtl_compens_en = rtl_compens_en + self.desc_improve = desc_improve + self.tolerance_for_same_wrong_scen = 2 + self.same_wrong_scen_times = 0 + # discriminator and corrector + self.discriminator_mode = discriminator_mode + self.corrector_mode = corrector_mode + self.discriminator = TB_discriminator(discriminator_mode) + self.corrector = TB_corrector(self.corrector_mode, self.pychecker_en, self.circuit_type) + self.improver = SPEC_improver(description, "naive", self.pychecker_en, self.main_model, self.circuit_type) + # rtl list and number + self.rtl_newly_gen_num = 0 + if self.rtl_list is None: + # self.rtl_num = rtl_num + self.set_rtl_num(rtl_num) + self.rtl_list_gen() + else: + # self.rtl_num = len(self.rtl_list) + self.set_rtl_num(len(self.rtl_list)) + self.scenario_matrix = None + self.wrong_scen_num = 0 # a small number as default, will be replaced later + self.previous_wrong_scen_num = 9999 # a very large number as default, will be replaced later + self.TB_syntax_error = False + # tb analysis results + self.tb_pass = None + self.wrong_col_index = None + self.correct_col_index = None + self.unsure_col_index = None + # next_action + self.next_action = None + self.iter_now = 0 + self.corrected = False # this means the TB has been corrected + if self.main_model is None: + logger.warning("main_model not found, may have trouble while correcting tb") + # record and runinfo + self.op_record = [] # record the order of the operations, similar to the var in autoline; will be added to the funccheck's op_record in the final runinfo. + + @property + def rtl_num(self): + """protected attr. rtl_num is initialized at the beginning and will not be changed""" + return self._rtl_num + + @log_localprefix("TBcheck") + def run(self): + """ + - the main function of TaskTBcheck + - the TB check stage contains several sub-stages: + - 1. TB discriminating + - 2. TB correcting + - output: will update the next action of the task, including: + - "pass": the TB already passed the selfcheck, will go to the evaluation stage + - "reboot": the whole pipeline will start from the very beginning + - workflow: inital discrimination -> correction-discrimination lloop -> pass or reboot + """ + # TODO: if error occurs, go to reboot. + # initial discrimination + tolerance = 1 + syntax_error = False + self.discriminate_TB() + if self.TB_syntax_error: + logger.negative("Testbench has syntax error, I give up. Reboot the whole process") + syntax_error = True + self.next_action = "reboot" + elif self.tb_pass: + logger.info("Testbench passed the funccheck") + self.next_action = "pass" + else: + # enter the correction loop, the initial TB has no syntax error when entering + if self.correct_max == 0: + logger.negative("No correction is allowed, I give up. Reboot the whole autoline process") + self.next_action = "reboot" + for self.iter_now in range(1, self.correct_max+1): + if (self.iter_now > 1) and (self.wrong_scen_num > self.previous_wrong_scen_num) and (not syntax_error): + # give up, the correction makes it worse + logger.negative(f"wrong scenarios increased ({self.wrong_scen_num} > {self.previous_wrong_scen_num}), I give up, quiting the funccheck stage...") + self.next_action = "reboot" + break + elif (self.iter_now > 1) and (self.wrong_scen_num == self.previous_wrong_scen_num) and (not syntax_error): + self.same_wrong_scen_times += 1 + if self.same_wrong_scen_times >= self.tolerance_for_same_wrong_scen: + logger.info(f"wrong scenarios not decreased for {self.tolerance_for_same_wrong_scen} times ({self.wrong_scen_num} = {self.previous_wrong_scen_num}), I give up, quiting the funccheck stage...") + self.next_action = "reboot" + break + else: + logger.info(f"wrong scenarios not decreased for {self.same_wrong_scen_times} times ({self.wrong_scen_num} = {self.previous_wrong_scen_num}), continue the correction") + self.correct_TB() + self.discriminate_TB() + if self.tb_pass: + logger.info("Testbench passed the funccheck after correction") + self.next_action = "pass" + break + elif self.TB_syntax_error: + # if the syntax error is from the corrector, we should roll back before correction + if tolerance > 0: + logger.negative(f"Testbench has syntax error after correction, I still have tolerance for that (tolerance={tolerance}). roll back and retry the self correction.") + self.TB_code_v, self.TB_code_py = self.TB_code_v_before_cor, self.TB_code_py_before_cor + tolerance -= 1 + syntax_error = True + else: + logger.negative("Testbench has syntax error after correction, I don't have tolerance. I give up. Reboot the whole autoline process") + self.next_action = "reboot" + syntax_error = True + break + self.next_action = "reboot" if self.iter_now == self.correct_max else None + if (self.next_action == "reboot") and (not syntax_error) and (self.desc_improve): + # the desc improver does not work well so we do not use it in this work + self.improve_SPEC() + logger.info(f"self funccheck finished. Next Action: [{self.next_action}]") + return self.next_action + + @log_localprefix("discriminator") + def discriminate_TB(self, no_any_files:bool=False): + """ + - check the correctness of the testbench and return the rtl analysis results in matrix form + - important data: the rtl list, the TB code + - update the following data: `scenario_matrix`, `tb_pass`, `wrong_col_index`, `correct_col_index`, `unsure_col_index`, `wrong_scen_num` + """ + rtl_dir_prefix = "RTL_" + self.op_record.append("discrim") + self.working_dir = os.path.join(self.task_dir, f"discrim_{self.iter_now}") + logger.info(f"Discriminating the testbench, NO.{self.iter_now} discrimination") + for i in range(self.rtl_compens_max_iter): + # the loop is for the case that too few RTL passed the syntax check, generate more rtls and recheck + failed_scenario_matrix = [] + # for rtl_code in self.rtl_list: + self.TB_syntax_error = True + syntax_error_rtl = [] + for rtl_idx, rtl_code in enumerate(self.rtl_list): + rtl_dir = os.path.join(self.working_dir, f"{rtl_dir_prefix}{rtl_idx+1}") + scenario_vector = self.run_testbench(rtl_dir, self.TB_code_v, rtl_code, self.TB_code_py, rtl_idx+1, self.runfiles_save and (not no_any_files)) + failed_scenario_matrix.append(scenario_vector) # like [[2, 5], [3, 4, 5]] + if scenario_vector != [-1]: + self.TB_syntax_error = False + else: + syntax_error_rtl.append(rtl_idx+1) + if syntax_error_rtl != []: + logger.info(f"RTL(s) {syntax_error_rtl} have syntax error during discrimination") + + if self.TB_syntax_error: + # there are two cases for TB syntax error: + # 1. this syntax error is from the previous stage, if so, we should directly reboot the whole autoline process + # 2. this syntax error is from the corrector, if so, we roll back to the version before correction and retry the correction + self.tb_pass = False + return None + + # the len of each scenario vector should be the same, thus we transform each vector into a onehot vector [[1,1,0,1,1,0], [1,1,1,0,0,0]] + self.scenario_matrix = self.failed_scenarios_to_onehot_array(failed_scenario_matrix, max_scen_idx=self.scenario_num, taskid=self.task_id) + if not no_any_files: + # save this matrix into the working dir in a human readable form + np.savetxt(os.path.join(self.working_dir, "scenario_matrix.csv"), self.scenario_matrix, delimiter=",", fmt="%d") + # save this matrix in a plot form + self.draw_scenario_matrix(self.scenario_matrix, self.task_id, os.path.join(self.working_dir, "scenario_matrix.png")) + + # we delete the syntax errored rtl, if no syntax error in TB + self.rtl_list = [rtl for rtl, scen in zip(self.rtl_list, failed_scenario_matrix) if scen != [-1]] + failed_scenario_matrix = [scen for scen in failed_scenario_matrix if scen != [-1]] + if len(self.rtl_list) < 0.5*self.rtl_num: + # too few RTL passed the syntax check + logger.info(f"too few RTL passed the syntax check ({len(self.rtl_list)}/{self.rtl_num}), I will generate more and recheck. This is not TB's fault.") + self.rtl_list, gen_num = self.gen_rtl(self.rtl_num-len(self.rtl_list), self.description, self.module_header, self.rtlgen_model, self.rtl_list) + self.rtl_newly_gen_num += gen_num + # delete the previous rtl dirs (dir start with rtl_dir_prefix and under the working dir) + for subdir in os.listdir(self.working_dir): + if subdir.startswith(rtl_dir_prefix) and os.path.isdir(os.path.join(self.working_dir, subdir)): + os.system(f"rm -rf {os.path.join(self.working_dir, subdir)}") + logger.info(f"re-discriminate the testbench with updated RTL list") + if i == self.rtl_compens_max_iter-1: + logger.info(f"no re-discrimination since the max iteration reached") + else: + break + + # discriminate the testbench according to the one hot matrix + self.tb_pass, self.wrong_col_index, self.correct_col_index, self.unsure_col_index = self.discriminator.discriminate(self.scenario_matrix) + self.previous_wrong_scen_num = self.wrong_scen_num + self.wrong_scen_num = len(self.wrong_col_index) + return self.tb_pass, self.wrong_col_index, self.correct_col_index, self.unsure_col_index + + @log_localprefix("corrector") + def correct_TB(self): + """ + - correct the testbench by using the RTL analysis results + """ + self.op_record.append("correct") + self.working_dir = os.path.join(self.task_dir, f"correct_{self.iter_now}") + self.TB_code_v_before_cor, self.TB_code_py_before_cor = self.TB_code_v, self.TB_code_py + self.TB_code_v, self.TB_code_py = self.corrector.correct(self.description, self.wrong_col_index, self.TB_code_v, self.main_model, self.TB_code_py, self.working_dir) + self.corrected = True + # self check if the testbench is corrected + # self.working_dir = os.path.join(self.task_dir, f"TBcheck_after_correct") + # self.discriminate_TB() + # if self.tb_pass: + # logger.info(f"[{self.task_id}] - Testbench passed the selfcheck after correction") + # self.next_action = "pass" + # else: + # logger.warning(f"[{self.task_id}] - Testbench failed the selfcheck after correction, failed scenarios: {self.wrong_col_index}") + + @log_localprefix("improver") + def improve_SPEC(self): + """ + - improve the specification of the task according to the discrimination and correction results + """ + self.op_record.append("improve") + self.working_dir = os.path.join(self.task_dir, "improve_Desc") + self.description = self.improver.improve(self.wrong_col_index, self.correct_col_index, self.TB_code_v, self.TB_code_py, working_dir=self.working_dir) + + + + @staticmethod + def run_testbench(dir, driver_code:str, DUT_code:str, checker_code:str, rtl_index:int, save_en:bool=True): + """ + - modified from autoline.py TBEval.run_testbench + - it has two mode: pychecker mode or verilog testbench mode + -input: + - dir: the dir to save the TB, DUT and pychecker code + - driver_code: str; the testbench code + - DUT_code: str; the DUT code + - checker_code: str; the pychecker code + - output: + - a list of failed scenarios (if rtl has syntax error, return [-1]) + """ + # iverilog part + # save the TB and DUT + os.makedirs(dir, exist_ok=True) + v_driver_path = os.path.join(dir, "driver.v") + py_checker_path = os.path.join(dir, "checker.py") + dut_path = os.path.join(dir, "DUT.v") + with open(v_driver_path, "w") as f: + f.write(driver_code) + with open(dut_path, "w") as f: + f.write(DUT_code) + iv_run_info = iv.iverilog_call_and_save(dir, silent=True) + if not iv_run_info[0]: + # logger.trace(f"RTL index [{rtl_index}]: Iverilog Compilation Failed, the PREREQUISITE of 'Evaluation' is no syntactic error from Testbench!!!") + # raise RuntimeError("Iverilog Compilation Failed") + return [-1] + with open(py_checker_path, "w") as f: + f.write(checker_code) + py_run_info = py.python_call_and_save(pypath=py_checker_path, silent=True) + if not py_run_info[0]: + # logger.trace(f"RTL index [{rtl_index}]: Iverilog Compilation Failed: the PREREQUISITE of 'Evaluation' is no syntactic error from Python code!!!") + # raise RuntimeError("Python Compilation Failed") + return [-1] + python_info_out = py_run_info[1]["out"] + python_info_out : str + # find the last ] in the out + last_bracket_end = python_info_out.rfind("]") + # find the last [ in the out + last_bracket_start = python_info_out.rfind("[") + # if the last [..] is a [], return [] + if last_bracket_end == last_bracket_start+1: + return [] + # extract the digits + failed_scenarios = python_info_out[last_bracket_start+1:last_bracket_end].replace("'", "").split(",") + # if the item is not pure digit such as 2b, then we only extract the digit part + failed_scenarios = [int("".join([char for char in scenario if char.isdigit()])) for scenario in failed_scenarios] + failed_scenarios = list(map(int, failed_scenarios)) + # if save_en false, we delete the dir + if not save_en: + os.system(f"rm -rf {dir}") + return list(set(failed_scenarios)) + + def rtl_list_gen(self)->list[str]: + """ + - generate the RTL list using LLM, will empty the old rtl list + - attr needed: description, module_header, rtl_num, llm_model + - attr changed: rtl_list + """ + self.rtl_list = [] + logger.info(f"rtl list not found, generating naive rtls for testbench checking") + self.rtl_list, gen_num = self.gen_rtl(self.rtl_num, self.description, self.module_header, self.rtlgen_model, self.rtl_list) + self.rtl_newly_gen_num += gen_num + # save the rtl list + save_path = os.path.join(self.task_dir, "rtl_list.json") + os.makedirs(self.task_dir, exist_ok=True) + ls.save_json_lines([{"task_id": self.task_id, "llmgen_RTL": self.rtl_list}], save_path) + + @staticmethod + def gen_rtl(num:int, description:str, header:str, llm_mode:str, rtl_list:list=[]): + """ + - input: + - num (int): the number of RTLs to generate + - description (str): the description of the rtl problem + - header (str): the header of the module + - llm (str): the llm model to use (official model name) + - rtl_list (list) [optional]: the newly generated RTLs will be appended to this list, can be empty + - output: + - rtl_list (list): the list of the newly generated RTLs (and the old ones, if have) + """ + rtl_gen_num = 0 + prompt = "Your task is to write a verilog RTL design according to the design specification. The infomation we have is the problem description that guides student to write the RTL code (DUT) and the header of the desired module. here is the problem description:\n" + prompt += description + prompt += "\nHere is the header of the desired module:\n" + prompt += header + prompt += "\nPlease only return the module code (header should be included) in verilog, please do not include any other words." + for i in range(num): + # call llm + answer = llm.llm_call(prompt, llm_mode)[0] + # extract the module code + module_code = llm.extract_code(answer, "verilog")[0] + # logger.trace(f"[{self.task_id}] - {i+1} RTLs generated") + rtl_list.append(module_code) + rtl_gen_num += 1 + logger.info("%d naive rtls generated"%(rtl_gen_num)) + return rtl_list, rtl_gen_num + + @staticmethod + def failed_scenarios_to_onehot_array(failed_scenarios:list[list], max_scen_idx:int|None=None, taskid:str=""): + """ + - input: [failed_scenarios:list[int]] (for example: [[1,2,3], [2,3,4], [1,3,4], [-1]]), if one failed scenario list is [-1], it means the rtl has syntax error, should be skipped + - output (np.array): a onehot array (for example: [[0,0,0,1], [1,0,0,0], [0,1,0,0], [-1,-1,-1,-1]]) (1 denots pass, 0 denotes fail, -1 means syntax error) + """ + + # find the max scenario index + listlen = len(failed_scenarios) + max_idx_given = max_scen_idx if max_scen_idx is not None else 1 + # we calculate the max_index_cal, and define the final max scenario index using max(max_index_cal, max_index_given) + max_idx_cal = max(map(lambda x: max(x) if x != [] else 0, failed_scenarios)) + if max_idx_cal in [-1, 0]: + # -1: all the scenarios in this rtl are -1 + # 0: usually not possible because the scenario index is from 1, but if exists, set to 1. + max_idx_cal = 1 + if failed_scenarios == list(map(lambda x: [], range(listlen))): + # this means all rtl passed + max_idx_cal = 1 # set to 1 otherwise the plot will be empty + max_idx = max(max_idx_cal, max_idx_given) + + # if the failed scenario list is [-1], then all the scenarios in this rtl are -1 + # create the onehot array + grid_map = [[1]*max_idx for _ in range(listlen)] + for rtl_idx, failed_scens in enumerate(failed_scenarios): + if failed_scens == [-1]: + grid_map[rtl_idx] = [-1]*max_idx + continue + for scen_idx in failed_scens: + grid_map[rtl_idx][scen_idx-1] = 0 + return np.array(grid_map) + + @staticmethod + def draw_scenario_matrix(scenario_matrix:np.ndarray, task_id:str, saving_path:str): + """ + - draw the 2D failed scenario array. The element in the array can only be 0, 1, -1. We use red for 0, green for 1, and gray for -1. + - if the scenario is empty, will return a gray color block. + """ + if len(scenario_matrix) == 0: + scenario_matrix = np.array([[-1]]) + # if the element in data not in [0, 1, -1], change the element to -1 + scenario_matrix = np.where(np.logical_or(scenario_matrix == 0, np.logical_or(scenario_matrix == 1, scenario_matrix == -1)), scenario_matrix, -1) + # get the RGB values for salmon, grey and mediumseagreen + salmon = mcolors.to_rgb("salmon") + grey = mcolors.to_rgb("grey") + mediumseagreen = mcolors.to_rgb("mediumseagreen") + color_mapping = { + 0: salmon, + 1: mediumseagreen, + -1: grey + } + rgb_image = np.array([[color_mapping[value] for value in row] for row in scenario_matrix]) + # assign the color to the scenario_matrix + for value, color in color_mapping.items(): + rgb_image[scenario_matrix == value] = color + plt.imshow(rgb_image) + plt.ylabel("RTL index") + plt.xlabel("Scenario index") + current_xticks = np.arange(scenario_matrix.shape[1]) + plt.xticks(current_xticks, current_xticks + 1) + current_yticks = np.arange(scenario_matrix.shape[0]) + plt.yticks(current_yticks, current_yticks + 1) + plt.title(f"[{task_id}] - Matrix of RTL-TB Scenario Correctness") + plt.savefig(saving_path) + plt.close() + + def update_description(self)->str: + """ + - will modify the description of the task according to the descrimination and correction results + """ + logger.info("the description of the task is updated") + return self.description + + def set_rtl_num(self, value:int): + self._rtl_num = value + + def __call__(self, *args, **kwds): + return self.run() + + +class TB_discriminator(): + """ + this class is used to discriminate the testbench according to the failing matrix + """ + def __init__(self, mode:str) -> None: + self.mode = mode + match self.mode: + case "col_full_wrong": + # the most naive mode; + # tb correction: if any scenario col is fully wrong, then the tb is wrong + # scenario correction: the scenarios that are fully wrong are wrong + pass + case "col_80_wrong": + # similar to the above, but the criterion is 80% wrong + pass + case "col_90_wrong": + # similar to the above, but the criterion is 90% wrong + pass + case "col_70_wrong": + # similar to the above, but the criterion is 70% wrong + pass + case "col_60_wrong": + # similar to the above, but the criterion is 60% wrong + pass + case "col_50_wrong": + # similar to the above, but the criterion is 50% wrong + pass + case "col_40_wrong": + # similar to the above, but the criterion is 40% wrong + pass + case "col_70_wrong_row_25_correct": + # similar to 70_wrong, but if 25% of the RTLs are fully correct, then the TB is correct + pass + case "col_50_wrong_row_25_correct": + # similar to 50_wrong, but if 25% of the RTLs are fully correct, then the TB is correct + pass + case "col_70_wrong_row_1_correct": + # similar to 70_wrong, but if 1% of the RTLs are fully correct, then the TB is correct + pass + case "col_70_wrong_row_10_correct": + # similar to 70_wrong, but if 10% of the RTLs are fully correct, then the TB is correct + pass + case _: + logger.critical("class discriminator - mode not found!!!") + + def discriminate(self, failed_matrix:np.ndarray)->tuple[bool, list[int], list[int], list[int]]: + """ + - input: the failed matrix of the testbench in onehot form + - output: + - the idexes in the scen list are starting from 1 + - bool: whether the testbench is correct + - list[int]: the list of the wrong scenarios + - list[int]: the list of the correct scenarios + - list[int]: the list of the scenarios that discriminator are not sure + - the -1 row will not be considered here. See function 'failed_scenarios_to_onehot_array' + """ + # first check if all the scenarios are [-1], which means the tb has syntax error + if np.all(failed_matrix == -1): + return None, [], [], [] + failed_matrix = failed_matrix[~np.all(failed_matrix == -1, axis=1)] + match self.mode: + case "col_full_wrong": + # check which column of the matrix is fully wrong (0) + wrong_col_index = np.where(np.all(np.isin(failed_matrix, [0, -1]), axis=0))[0] + 1 + correct_col_index = np.where(np.all(np.isin(failed_matrix, [1, -1]), axis=0))[0] + 1 + unsure_col_index = np.where(np.any(failed_matrix == 0, axis=0) & np.any(failed_matrix == 1, axis=0))[0] + 1 + tb_pass = len(wrong_col_index) == 0 # as long as there is no fully wrong column, the tb is correct (loose criterion) + logger.match_level(tb_pass, "positive", "negative", f"TB_discriminating finished, TB {"passed" if tb_pass else "failed"}, wrong scenarios: {wrong_col_index}, scenario pass ratio: {len(correct_col_index)}/{len(failed_matrix[0])}") + # my_log = logger.positive if tb_pass else logger.negative + # my_log.info(f"TB_discriminating finished, TB {"passed" if tb_pass else "failed"}, wrong scenarios: {wrong_col_index}") + return tb_pass, wrong_col_index, correct_col_index, unsure_col_index + case "col_80_wrong": + # check which column of the matrix is 80% wrong (0) + wrong_col_index = np.where(np.sum(failed_matrix == 0, axis=0) >= 0.8*len(failed_matrix))[0] + 1 + correct_col_index = np.where(np.all(np.isin(failed_matrix, [1, -1]), axis=0))[0] + 1 + unsure_col_index = np.where(np.logical_and(np.sum(failed_matrix == 0, axis=0) < 0.8*len(failed_matrix), np.any(failed_matrix == 0, axis=0)))[0] + 1 + tb_pass = len(wrong_col_index) == 0 + logger.match_level(tb_pass, "positive", "negative", f"TB_discriminating finished, TB {"passed" if tb_pass else "failed"}, wrong scenarios: {wrong_col_index}, scenario pass ratio: {len(correct_col_index)}/{len(failed_matrix[0])}") + return tb_pass, wrong_col_index, correct_col_index, unsure_col_index + case "col_90_wrong": + # check which column of the matrix is 90% wrong (0) + wrong_col_index = np.where(np.sum(failed_matrix == 0, axis=0) >= 0.9*len(failed_matrix))[0] + 1 + correct_col_index = np.where(np.all(np.isin(failed_matrix, [1, -1]), axis=0))[0] + 1 + unsure_col_index = np.where(np.logical_and(np.sum(failed_matrix == 0, axis=0) < 0.9*len(failed_matrix), np.any(failed_matrix == 0, axis=0)))[0] + 1 + tb_pass = len(wrong_col_index) == 0 + logger.match_level(tb_pass, "positive", "negative", f"TB_discriminating finished, TB {"passed" if tb_pass else "failed"}, wrong scenarios: {wrong_col_index}, scenario pass ratio: {len(correct_col_index)}/{len(failed_matrix[0])}") + return tb_pass, wrong_col_index, correct_col_index, unsure_col_index + case "col_70_wrong": + # check which column of the matrix is 70% wrong (0) + wrong_col_index = np.where(np.sum(failed_matrix == 0, axis=0) >= 0.7*len(failed_matrix))[0] + 1 + correct_col_index = np.where(np.all(np.isin(failed_matrix, [1, -1]), axis=0))[0] + 1 + unsure_col_index = np.where(np.logical_and(np.sum(failed_matrix == 0, axis=0) < 0.7*len(failed_matrix), np.any(failed_matrix == 0, axis=0)))[0] + 1 + tb_pass = len(wrong_col_index) == 0 + logger.match_level(tb_pass, "positive", "negative", f"TB_discriminating finished, TB {"passed" if tb_pass else "failed"}, wrong scenarios: {wrong_col_index}, scenario pass ratio: {len(correct_col_index)}/{len(failed_matrix[0])}") + return tb_pass, wrong_col_index, correct_col_index, unsure_col_index + case "col_70_wrong_row_25_correct": + # check which column of the matrix is 70% wrong (0) + wrong_col_index = np.where(np.sum(failed_matrix == 0, axis=0) >= 0.7*len(failed_matrix))[0] + 1 + correct_col_index = np.where(np.all(np.isin(failed_matrix, [1, -1]), axis=0))[0] + 1 + unsure_col_index = np.where(np.logical_and(np.sum(failed_matrix == 0, axis=0) < 0.7*len(failed_matrix), np.any(failed_matrix == 0, axis=0)))[0] + 1 + tb_pass = len(wrong_col_index) == 0 + if np.sum(np.all(failed_matrix == 1, axis=1)) >= 0.25*len(failed_matrix): + tb_pass = True + logger.match_level(tb_pass, "positive", "negative", f"TB_discriminating finished, TB {"passed" if tb_pass else "failed"}, wrong scenarios: {wrong_col_index}, scenario pass ratio: {len(correct_col_index)}/{len(failed_matrix[0])}") + return tb_pass, wrong_col_index, correct_col_index, unsure_col_index + case "col_70_wrong_row_1_correct": + # check which column of the matrix is 70% wrong (0) + wrong_col_index = np.where(np.sum(failed_matrix == 0, axis=0) >= 0.7*len(failed_matrix))[0] + 1 + correct_col_index = np.where(np.all(np.isin(failed_matrix, [1, -1]), axis=0))[0] + 1 + unsure_col_index = np.where(np.logical_and(np.sum(failed_matrix == 0, axis=0) < 0.7*len(failed_matrix), np.any(failed_matrix == 0, axis=0)))[0] + 1 + tb_pass = len(wrong_col_index) == 0 + if np.sum(np.all(failed_matrix == 1, axis=1)) >= 0.01*len(failed_matrix): + tb_pass = True + logger.match_level(tb_pass, "positive", "negative", f"TB_discriminating finished, TB {"passed" if tb_pass else "failed"}, wrong scenarios: {wrong_col_index}, scenario pass ratio: {len(correct_col_index)}/{len(failed_matrix[0])}") + return tb_pass, wrong_col_index, correct_col_index, unsure_col_index + case "col_70_wrong_row_10_correct": + # check which column of the matrix is 70% wrong (0) + wrong_col_index = np.where(np.sum(failed_matrix == 0, axis=0) >= 0.7*len(failed_matrix))[0] + 1 + correct_col_index = np.where(np.all(np.isin(failed_matrix, [1, -1]), axis=0))[0] + 1 + unsure_col_index = np.where(np.logical_and(np.sum(failed_matrix == 0, axis=0) < 0.7*len(failed_matrix), np.any(failed_matrix == 0, axis=0)))[0] + 1 + tb_pass = len(wrong_col_index) == 0 + if np.sum(np.all(failed_matrix == 1, axis=1)) >= 0.1*len(failed_matrix): + tb_pass = True + logger.match_level(tb_pass, "positive", "negative", f"TB_discriminating finished, TB {"passed" if tb_pass else "failed"}, wrong scenarios: {wrong_col_index}, scenario pass ratio: {len(correct_col_index)}/{len(failed_matrix[0])}") + return tb_pass, wrong_col_index, correct_col_index, unsure_col_index + case "col_60_wrong": + # check which column of the matrix is 60% wrong (0) + wrong_col_index = np.where(np.sum(failed_matrix == 0, axis=0) >= 0.6*len(failed_matrix))[0] + 1 + correct_col_index = np.where(np.all(np.isin(failed_matrix, [1, -1]), axis=0))[0] + 1 + unsure_col_index = np.where(np.logical_and(np.sum(failed_matrix == 0, axis=0) < 0.6*len(failed_matrix), np.any(failed_matrix == 0, axis=0)))[0] + 1 + tb_pass = len(wrong_col_index) == 0 + logger.match_level(tb_pass, "positive", "negative", f"TB_discriminating finished, TB {"passed" if tb_pass else "failed"}, wrong scenarios: {wrong_col_index}, scenario pass ratio: {len(correct_col_index)}/{len(failed_matrix[0])}") + return tb_pass, wrong_col_index, correct_col_index, unsure_col_index + case "col_50_wrong": + # check which column of the matrix is 50% wrong (0) + wrong_col_index = np.where(np.sum(failed_matrix == 0, axis=0) >= 0.5*len(failed_matrix))[0] + 1 + correct_col_index = np.where(np.all(np.isin(failed_matrix, [1, -1]), axis=0))[0] + 1 + unsure_col_index = np.where(np.logical_and(np.sum(failed_matrix == 0, axis=0) < 0.5*len(failed_matrix), np.any(failed_matrix == 0, axis=0)))[0] + 1 + tb_pass = len(wrong_col_index) == 0 + logger.match_level(tb_pass, "positive", "negative", f"TB_discriminating finished, TB {"passed" if tb_pass else "failed"}, wrong scenarios: {wrong_col_index}, scenario pass ratio: {len(correct_col_index)}/{len(failed_matrix[0])}") + return tb_pass, wrong_col_index, correct_col_index, unsure_col_index + case "col_50_wrong_row_25_correct": + # check which column of the matrix is 50% wrong (0) + wrong_col_index = np.where(np.sum(failed_matrix == 0, axis=0) >= 0.5*len(failed_matrix))[0] + 1 + correct_col_index = np.where(np.all(np.isin(failed_matrix, [1, -1]), axis=0))[0] + 1 + unsure_col_index = np.where(np.logical_and(np.sum(failed_matrix == 0, axis=0) < 0.5*len(failed_matrix), np.any(failed_matrix == 0, axis=0)))[0] + 1 + tb_pass = len(wrong_col_index) == 0 + if np.sum(np.all(failed_matrix == 1, axis=1)) >= 0.25*len(failed_matrix): + tb_pass = True + logger.match_level(tb_pass, "positive", "negative", f"TB_discriminating finished, TB {"passed" if tb_pass else "failed"}, wrong scenarios: {wrong_col_index}, scenario pass ratio: {len(correct_col_index)}/{len(failed_matrix[0])}") + return tb_pass, wrong_col_index, correct_col_index, unsure_col_index + case "col_40_wrong": + # check which column of the matrix is 40% wrong (0) + wrong_col_index = np.where(np.sum(failed_matrix == 0, axis=0) >= 0.4*len(failed_matrix))[0] + 1 + correct_col_index = np.where(np.all(np.isin(failed_matrix, [1, -1]), axis=0))[0] + 1 + unsure_col_index = np.where(np.logical_and(np.sum(failed_matrix == 0, axis=0) < 0.4*len(failed_matrix), np.any(failed_matrix == 0, axis=0)))[0] + 1 + tb_pass = len(wrong_col_index) == 0 + logger.match_level(tb_pass, "positive", "negative", f"TB_discriminating finished, TB {"passed" if tb_pass else "failed"}, wrong scenarios: {wrong_col_index}, scenario pass ratio: {len(correct_col_index)}/{len(failed_matrix[0])}") + return tb_pass, wrong_col_index, correct_col_index, unsure_col_index + case _: + logger.critical("TB discriminator - mode not found!!!") + raise RuntimeError("TB discriminator - mode not found!!!") + +COR_PROMPT_1 = """Your task is to correct the testbench according to the failing scenarios. the information we have is the failed/passed scenarios of the testbench, the problem description and the testbench code. +the testbench code is consisted of both verilog and python code. The verilog code aims to generate test stimulus (under test scenarios) and drive the DUT to generate the output signal; the python code aims to check if the output vector from the DUT is correct. +ATTENTION: The python code contains error, and your target is to find it and tell me how to correct it (you don't need to give me the code in this stage). +""" + +HINT_SEQ = """ +Hints - explaination of the given python code: +the python class "GoldenDUT": This python class can represent the golden DUT (the ideal one). In "GoldenDUT", following methods are defined: +- 1. a method "def __init__(self)": Set the inner states/values of the golden DUT. These values have suffix "_reg". The initial value of these inner values is "x", but later will be digits. The "__init__" method has no input parameters except "self". +- 2. a method "def load(self, signal_vector)": This method is to load the important input signals and the inner values of "GoldenDUT" shall change according to the input signals. There is no clock signal in the input signal vector, every time the "load" method is called, it means a new clock cycle. The initial values "x" should be changed according to the input signals. This method has no return value. +- 3. a method "def check(self, signal_vector)": This method is to determine the expected output values and compare them with output signals from DUT. It should return True or False only. +""" + +HINT_CMB = """ +Hints - explaination of the given python code: +The given python code contains one class "GoldenDUT". this python class can represent the golden DUT (the ideal one). By calling the inner method "check", the signal vector from DUT will be checked. The details of the golden DUT are as follows: + +- a. a method "def __init__(self)". Set the inner states/values of the golden DUT. The "__init__" method has no input parameters except "self". +- b. a method "def load(self, signal_vector)". This method is to load the important input signals and get the expected output signals. it should return the expected output values. It can call other methods to help computing the expected output. It will be called by other inner methods later. +- c. a method "def check(self, signal_vector)". This method is to call "load" to get the expected output values, and compare them with output signals from DUT. It should return True or False only. It can call other methods to help checking. +- d. other methods, they can be called by "__init__", "load" or "check". +- e. the input of "load" and "check" is the signal vector. The signal vector is a dictionary, the key is the signal name, the value is the signal value. +""" + +COR_PROMPT_2_PART1 = """ +please correct the python code according to the following rules: + +PYTHON code rule: please do not change the original high level structure of the python code. i.e., if python code only contains one class and several functions such as init, load, check and more, only modify the implementation of the function, but do not change the name or delete the functions/class methods. you can add new class methods or functions if needed. you can use python libraries such as numpy or math. + +""" +COR_PROMPT_2_PART2 = """ +i.e., your python code format in response should still be like: + +class : + def __init__(self): + ...(omitted) + + def load(self, ...): + ... + + def check(self, ...): + ... + + def (self, ...): + ... + +ATTENTION: please give me the corrected python code according to our previous conversation and the hints above. please give me the corrected full python code (not the part but the whole python code like I give you in our previous conversation). +""" + +class TB_corrector(): + def __init__(self, mode:str, pychecker_en:bool, circuit_type:str="") -> None: + self.mode = mode + self.pychecker_en = pychecker_en + circuit_type_dict = {"CMB": "combinational", "SEQ": "sequential"} + self.circuit_type = circuit_type_dict.get(circuit_type, "unknown") + # logger.debug(f"TB_corrector class - mode: {self.mode}, pychecker_en: {self.pychecker_en}, circuit_type: {self.circuit_type}; The input circuit type is {circuit_type}") + match self.mode: + case "naive": + # the most naive mode; + pass + case _: + logger.critical("TB_corrector class - mode not found!!!") + + def correct(self, description, failed_scenarios, TB_code_v:str, llm_model:str, TB_code_py:str|None=None, working_dir:str=None) -> tuple[str, str]: + match self.mode: + case "naive": + if self.pychecker_en: + TB_code_py = self._py_focus(TB_code_py, before=True) + py_code_hint = HINT_CMB if self.circuit_type == "combinational" else HINT_SEQ + prompt = COR_PROMPT_1 + prompt += "Here is the problem description:\n" + prompt += description + prompt += "\nHere is the testbench code:\n" + prompt += "ATTENTION: the following scenarios are wrong: " + str(failed_scenarios) + "\n" + # circuit type + prompt += f"the circuit type of this task is {self.circuit_type}\n" + prompt += "Here is the verilog code. it contains the meaning of each scenario. you can combine the wong scenario info above and the following code to better understand the reason of failing:\n" + prompt += TB_code_v + prompt += "\nHere is the python code, it contains error, please combine it with the wrong scenario info and the verilog code to understand:\n" + prompt += TB_code_py + prompt += "\nHere is some hints for your better understanding of the python codes above:" + prompt += py_code_hint + prompt += "\nplease reply me with the following steps:" + prompt += "\n1. please analyze the reason of the failed scenarios. If possible, please find the in common between the failed scenarios." + prompt += f"\n2. please analyze which part of the python code is related to the failed test scenarios ({str(failed_scenarios)})." + prompt += "\n3. please tell me how to correct the wrong part (in natural language, do not give me the complete code implementation. please explain it in English.)" + prompt += "\nhere is an example of the reply:" + prompt += "\n1. the failed scenarios are all related to the same signal x\n2. the mid part of the function_X is related to the failed scenarios\n3. the correct logic of signal x should be y." + logger.info("naive corrector mode begins") + answer = llm.llm_call(prompt, llm_model)[0] + prompt_2 = COR_PROMPT_2_PART1 + py_code_hint + COR_PROMPT_2_PART2 + message = [{"role": "user", "content": prompt}, {"role": "assistant", "content": answer}, {"role": "user", "content": prompt_2}] + answer_2, more_info = llm.llm_call(message, llm_model) + # if "VERILOG" in answer_2: + # TB_code_v = llm.extract_code(answer_2, "verilog")[0] + # else: + # TB_code_py = llm.extract_code(answer_2, "python")[0] + TB_code_py = llm.extract_code(answer_2, "python")[0] + TB_code_py = self._py_focus(TB_code_py, before=False) + if working_dir is not None: + ls.save_messages_to_txt(more_info["messages"], os.path.join(working_dir, "conversation.txt")) + ls.save_code(TB_code_v, os.path.join(working_dir, "TB.v")) + if TB_code_py is not None: + ls.save_code(TB_code_py, os.path.join(working_dir, "TB.py")) + logger.info("naive corrector mode ends; conversation and codes saved") + else: + logger.critical("TB_corrector - pychecker not enabled") + raise RuntimeError("TB_corrector - pychecker not enabled") + return TB_code_v, TB_code_py + case _: + logger.critical("TB_corrector - mode not found!!!") + raise RuntimeError("TB_corrector - mode not found!!!") + + def _py_focus(self, code:str, before:bool): + """ + - imigrated from TB2_syncheck.py + - code: the code under debug / after debug + - before: True, if before debug, will split the code; False, if after debug, will restore the code + """ + KEY_WORDs_1 = "def check_dut(vectors_in):\n golden_dut = GoldenDUT()\n failed_scenarios = []" + KEY_WORDs_2 = "\ndef SignalTxt_to_dictlist" + if before: + key_words = KEY_WORDs_1 if KEY_WORDs_1 in code else KEY_WORDs_2 # for compatibility with the old version + if key_words not in code: + py_code_focus = code + self._py_code_nofocus = "" + else: + py_code_focus = code.split(key_words)[0] + self._py_code_nofocus = key_words + code.split(key_words)[1] + return py_code_focus + else: + return code + self._py_code_nofocus + +IMPR_PROMPT_1 = """Your task is to improve the quality of an RTL problem description using the following given information. Our final target is using the description to generate a testbench for the RTL design. Currently we already have the testbench but it is not perfect correct. Now in this stage the target is to generate a better description. +The information we have the is original RTL description, the testbench code. The testbench code includes the verilog code for test scenario generation, and the python code for checking the output vector.The verilog code aims to generate test stimulus (under test scenarios) and drive the DUT to generate the output signal; the python code aims to check if the output vector from the DUT is correct. +Attention: the testbench we provide is not the perfect one, and it contains error. However, we already know the test scenarios where the testbench works correctly and the scenarios where the testbench has problem. We will provide you the scenarios index later. +Here, firstly, is the problem description to be imporved: +\n""" + +DESC_MARK_BEGIN = "***description begins***" +DESC_MARK_END = "***description ends***" + +DESC_STEP_INSTRUCT = f""" +please reply me with the following steps: +1. please analyze which part of the testbench (especially the python checker code) is correct and can be used to improve the description. +2. please analyze how can we improve the descriptin. for example, which part of the technical details can be more detailed, which part can be more clear, which part can be more concise. +3. please provide the improved complete description. We will directly use it in the later stages. +the format of description should be like: +{DESC_MARK_BEGIN} +... (the improved description, should be complete) +{DESC_MARK_END} +""" + +DESC_FINAL_INSTRUCT = f""" +ATTENTION: please know that the provided testbench is not perfect and may contains many errors. Thus, your modification on the description should not change the function of the original description. When there are conflicts between the testbench and the description, always believe the description is correct. Do not delete the information in the description, but you can rewrite it in a better way. You can also add more details to it. But NEVER mention any scenario index because the scenarios will not be the same at the next stage. +when you answer the last question (provide the improved complete description), the descriptino should start with "{DESC_MARK_BEGIN}" and end with "{DESC_MARK_END}". Only in this way can we recognize the improved description. +""" +class SPEC_improver(): + def __init__(self, description, mode:str, pychecker_en:bool, llm_model:str, circuit_type:str="") -> None: + self.description = description + self.mode = mode + self.pychecker_en = pychecker_en + circuit_type_dict = {"CMB": "combinational", "SEQ": "sequential"} + self.llm_model = llm_model + self.circuit_type = circuit_type_dict.get(circuit_type, "unknown") + + def improve(self, wrong_scenarios, correct_scenarios, TB_code_v:str, TB_code_py:str|None=None, working_dir:str|None=None) -> str: + # not implemented yet + match self.mode: + case "naive": + logger.info("naive description improver mode begins") + prompt = "" + prompt += IMPR_PROMPT_1 + prompt += DESC_MARK_BEGIN + "\n" + prompt += self.description + "\n" + prompt += DESC_MARK_END + "\n" + prompt += "\nHere is the testbench codes:\n" + prompt += "ATTENTION: the following scenarios are wrong: " + str(wrong_scenarios) + "\n" + prompt += "ATTENTION: the following scenarios are correct, you can rely on these scenarios to improve the description: " + str(correct_scenarios) + "\n" + prompt += TB_code_v + "\n" + if self.pychecker_en: + prompt += f"\nHere is the python code (python checker). please note that the python checker has correct function under the scenarios {str(correct_scenarios)}, but wrong under the scenarios {str(wrong_scenarios)}:\n" + prompt += TB_code_py + "\n" + prompt += DESC_STEP_INSTRUCT + prompt += DESC_FINAL_INSTRUCT + message = [{"role": "user", "content": prompt}] + answer, more_info = llm.llm_call(message, self.llm_model) + try: + improved_description = answer.split(DESC_MARK_BEGIN)[1].split(DESC_MARK_END)[0] + if improved_description == "": + improved_description = self.description + except: + improved_description = self.description + if working_dir is not None: + ls.save_messages_to_txt(more_info["messages"], os.path.join(working_dir, "conversation.txt")) + # save description + with open(os.path.join(working_dir, "description.txt"), "w") as f: + f.write(improved_description) + logger.info("naive description improver mode ends") + return improved_description + case "hint": + logger.info("description improver 'hint' mode begins") + + return self.description + +def test(): + from loguru import logger + failed_scenarios = [ + [1,3,5], + [-1], + [2,3] + ] + max_scenario = 7 + onehot_array = TaskTBcheck.failed_scenarios_to_onehot_array(failed_scenarios, max_scenario) + print(np.array(onehot_array)) + my_disc = TB_discriminator("col_full_wrong") + print(my_disc.discriminate(np.array(onehot_array))) + diff --git a/autoline/TB4_eval.py b/autoline/TB4_eval.py new file mode 100644 index 0000000..b500547 --- /dev/null +++ b/autoline/TB4_eval.py @@ -0,0 +1,245 @@ +""" +Description : This is the testbench eval stage in autoline +Author : Ruidi Qiu (r.qiu@tum.de) +Time : 2024/7/24 11:24:43 +LastEdited : 2024/8/28 21:08:21 +""" + + +import os +import iverilog_call as iv +import python_call as py +from loader_saver import autologger as logger +from loader_saver import log_localprefix +from utils.utils import Timer, get_time + +TC_PASS_CHECK_LIST_TB_GEN = ["All test cases passed", "all test cases passed", "All Test Cases Passed"] +TC_PASS_CHECK_LIST_TB_GOLDEN = ['Mismatches: 0 in ', 'Hint: Total mismatched samples is 0 out of'] +TC_PASS_CHECK_LIST_PYCHECKER = ["[]"] + +class TaskTBeval(): + """ + ### description + - this is the evaluation stage of our pipeline; the priority of this stage is that TB is generated and the empty DUT compilation is passed; + - please use `try` to catch the exception of this function. + - this module is independent from the previous modules. + #### input + - task_id: the name of the problem + - root_dir: the dir of one problem + - TB_gen: the testbench under evaluation (str) + - TB_golden: the golden testbench (str) + - DUT_golden: the golden RTL DUT (str) + - DUT_mutant_list: the list of RTL DUT mutants modified from DUT_golden;[str] + #### output + - dict + - "Eval1_pass" : bool (whether the golden RTL checking passed) + - "Eval2_pass" : bool (whether the golden TB comparison on RTL mutants passed) + - "Eval2_failed_mutant_idxes" : list of int (the index of the failed mutants) + """ + """main structure: run(), run_Eval1(), run_Eval2()""" + def __init__(self, task_id: str, task_dir: str, TB_gen: str, TB_golden:str=None, DUT_golden:str=None, DUT_mutant_list:list=None, DUT_gptgen_list:list = None, pychecker_en:bool = False, pychecker_code:str = "", runfiles_save:bool = True): + self.task_id = task_id + self.task_dir = task_dir + self.TB_gen = TB_gen + self.TB_golden = TB_golden + self.DUT_golden = DUT_golden + self.DUT_mutant_list = DUT_mutant_list + self.DUT_gptgen_list = DUT_gptgen_list + self.pychecker_en = pychecker_en + self.save_en = runfiles_save + self.TB_gen_mode = "TB_gen" if not self.pychecker_en else "Pychecker" + self.pychecker_code = pychecker_code + self.working_dir = "" + # Eval1 related + self.Eval1_exist = False + # self.Eval1_dir = task_dir + "eval1_GoldenRTL/" + self.Eval1_dir = os.path.join(task_dir, "eval1_GoldenRTL") + self.Eval1_results = None + self.Eval1_pass = None + # Eval2 related + self.Eval2_exist = False + # self.Eval2_dir = task_dir + "eval2_GoldenTB_and_mutants/" + self.Eval2_dir = os.path.join(task_dir, "eval2_GoldenTB_and_mutants") + self.Eval2_pass = None + self.Eval2_failed_mutant_idx = None + self.Eval2_passed_mutant_idx = None + # Eval2b related + self.Eval2b_exist = False + # self.Eval2b_dir = task_dir + "eval2b_GPTgenTB/" + self.Eval2b_dir = os.path.join(task_dir, "eval2b_GPTgenTB") + self.Eval2b_pass = None + self.Eval2b_failed_mutant_idx = None + self.Eval2b_passed_mutant_idx = None + + @log_localprefix("TBeval") + def run(self): + # Eval 1 + if self.DUT_golden is not None: + self.run_Eval1() + if self.Eval1_pass: + # Eval 2 + if self.TB_golden is not None and self.DUT_mutant_list is not None: + self.run_Eval2(mode="mutant") + # Eval 2b + if self.TB_golden is not None and self.DUT_gptgen_list is not None: + self.run_Eval2(mode="gptgen") + else: + logger.info("[%s] Eval 2/2b is skipped because Eval 1 failed" % (self.task_id)) + self.clean_wave_vcd() # some golden TBs may generate wave.vcd files + + def run_Eval1(self): + silent = True + ### Eval 1: Golden RTL checking + logger.info("Eval 1: Golden RTL checking begins") + self.Eval1_pass = self.run_testbench(self.Eval1_dir, self.TB_gen, self.DUT_golden, self.TB_gen_mode, self.pychecker_code, raise_when_fail=True, save_en=self.save_en) + logger.match_level(self.Eval1_pass, "positive", "failed", "Eval 1: Golden RTL checking %s!" % ("passed" if self.Eval1_pass else "failed")) + # my_log = logger.positive if self.Eval1_pass else logger.failed + # my_log("[%s] Eval 1: Golden RTL checking %s!" % (self.task_id, "passed" if self.Eval1_pass else "failed")) + self.Eval1_exist = True + + def run_Eval2(self, mode:str="mutant"): + """ mode: "mutant" or "gptgen" """ + silent = True + assert mode in ["mutant", "gptgen"], "Invalid mode in run_Eval2: " + mode + if mode == "mutant": # Eval2 + print_str = "Eval 2: Golden TB checking on RTL mutants" + mutant_subdir_name = "mutant" + DUT_list = self.DUT_mutant_list + eval_dir = self.Eval2_dir + elif mode == "gptgen": # Eval2b + print_str = "Eval 2b: Golden TB checking on GPT generated RTL codes" + mutant_subdir_name = "gptgen_DUT" + DUT_list = self.DUT_gptgen_list + eval_dir = self.Eval2b_dir + ### Eval 2: Golden TB comparison on RTL mutants + logger.info(print_str) + mutant_results = [] + for idx, DUT_mutant in enumerate(DUT_list): + # mutant_subdir = eval_dir + "%s_%d/"%(mutant_subdir_name, idx+1) + mutant_subdir = os.path.join(eval_dir, "%s_%d"%(mutant_subdir_name, idx+1)) + # GoldenTB_subsubdir = mutant_subdir + "GoldenTB/" + GoldenTB_subsubdir = os.path.join(mutant_subdir, "GoldenTB") + # GenedTB_subsubdir = mutant_subdir + "GeneratedTB/" + GenedTB_subsubdir = os.path.join(mutant_subdir, "GeneratedTB") + try: #in case the mutant has syntax error + TBgolden_pass = self.run_testbench(GoldenTB_subsubdir, self.TB_golden, DUT_mutant, "TB_golden", save_en=self.save_en) + except: + TBgolden_pass = False + try: + TBgen_pass = self.run_testbench(GenedTB_subsubdir, self.TB_gen, DUT_mutant, self.TB_gen_mode, self.pychecker_code, save_en=self.save_en) + except: + TBgen_pass = False + if not TBgolden_pass and not TBgen_pass: + mutant_pass = True + elif TBgolden_pass and TBgen_pass: + mutant_pass = True + else: + mutant_pass = False + mutant_results.append(mutant_pass) + eval_pass = all(mutant_results) + failed_mutant_idx = [idx + 1 for idx, result in enumerate(mutant_results) if not result] + passed_mutant_idx = [idx + 1 for idx, result in enumerate(mutant_results) if result] + if mode == "mutant": + self.Eval2_pass, self.Eval2_failed_mutant_idx, self.Eval2_passed_mutant_idx, self.Eval2_exist = eval_pass, failed_mutant_idx, passed_mutant_idx, True + elif mode == "gptgen": + self.Eval2b_pass, self.Eval2b_failed_mutant_idx, self.Eval2b_passed_mutant_idx, self.Eval2b_exist = eval_pass, failed_mutant_idx, passed_mutant_idx, True + result = "perfectly passed" if eval_pass else ("finished (%d/%d)" % (len(passed_mutant_idx), len(mutant_results))) + my_log = logger.success if (eval_pass or (len(passed_mutant_idx)/len(mutant_results)>=0.8)) else logger.failed + my_log("%s %s!" % (print_str, result)) + + def run_testbench(self, dir, TB_code, DUT_code, TB_type, pychecker_code = "", raise_when_fail = False, save_en = True): + """ + it has two mode: pychecker mode or verilog testbench mode + -input: + - dir: the dir to save the TB, DUT and pychecker code + - TB_code: str; the testbench code + - DUT_code: str; the DUT code + - TB_type: str: TB_gen, TB_golden, Pychecker + - pychecker_code: str; the pychecker code + - output: + - pass: bool; if the DUT passed the testbench + """ + # iverilog part + # save the TB and DUT + assert TB_type in ["TB_gen", "TB_golden", "Pychecker"], "Invalid TB_type in run_testbench: " + TB_type + os.makedirs(dir, exist_ok=True) + self.working_dir = dir + with open(self.TB_path, "w") as f: + f.write(TB_code) + with open(self.DUT_path, "w") as f: + f.write(DUT_code) + iv_run_info = iv.iverilog_call_and_save(dir, silent=True) + if raise_when_fail: + assert iv_run_info[0], "%s Iverilog Compilation Failed: the PREREQUISITE of 'Evaluation' is no syntactic error from Testbench!!!"%(TB_type) + # pychecker part (if enabled) + if TB_type == "Pychecker": + with open(self.PY_path, "w") as f: + f.write(pychecker_code) + py_run_info = py.python_call_and_save(pypath=self.PY_path, silent=True) + if raise_when_fail: + assert py_run_info[0], "%s Python Compilation Failed: the PREREQUISITE of 'Evaluation' is no syntactic error from Python code!!!"%(TB_type) + # check if the DUT passed the testbench + TC_pass = self.TC_pass_from_TC_out(sim_pass=True, sim_out=py_run_info[1]["out"], TB_type="Pychecker") & iv_run_info[0] & py_run_info[0] + else: + TC_pass = self.TC_pass_from_TC_out(sim_pass=True, sim_out=iv_run_info[4]["out"], TB_type=TB_type) & iv_run_info[0] + if not save_en: + # os.system(f"rm -rf {dir}") + cmd = f"find {dir} -type f ! -name 'run_info*'" + r" -exec rm -f {} +" + os.system(cmd) + return TC_pass + + def clean_wave_vcd(self): + """clean the .vcd files in the task_dir""" + # clean_dir = self.task_dir[:-1] if self.task_dir.endswith("/") else self.task_dir + clean_dir = self.task_dir + for root, dirs, files in os.walk(clean_dir): + for file in files: + # clean wave.vcd + if file.endswith(".vcd"): + os.remove(os.path.join(root, file)) + + @property + def TB_path(self): + # return self.working_dir + self.task_id + "_tb.v" + return os.path.join(self.working_dir, self.task_id + "_tb.v") + + @property + def DUT_path(self): + # return self.working_dir + self.task_id + ".v" + return os.path.join(self.working_dir, self.task_id + ".v") + + @property + def PY_path(self): + # return self.working_dir + self.task_id + "_tb.py" + return os.path.join(self.working_dir, self.task_id + "_tb.py") + + @staticmethod + def TC_pass_from_TC_out(sim_pass: bool, sim_out: str, TB_type="TB_gen"): + """ + get the information if DUT passed all the test cases from the testbench + #### input + - sim_pass: bool; if TB passed the compilation. if not, will return False without check + - sim_out: the simulation output message; + - TB_ty: "TB_gen" or "TB_golden" or "Pychecker"; the type of the testbench + """ + if not sim_pass: + return False + assert TB_type in ["TB_gen", "TB_golden", "Pychecker"], "Invalid TB_type during 'TC_pass_from_TC_out': " + TB_type + tc_pass_check_list_dict = {"TB_gen": TC_PASS_CHECK_LIST_TB_GEN, "TB_golden": TC_PASS_CHECK_LIST_TB_GOLDEN, "Pychecker": TC_PASS_CHECK_LIST_PYCHECKER} + tc_pass_check_list = tc_pass_check_list_dict[TB_type] + if TB_type in ["TB_gen", "TB_golden"]: + for check_str in tc_pass_check_list: + if check_str in sim_out: + return True + return False + elif TB_type in ['Pychecker']: + # check if the last [] contains any element + # find the last ] in the out + last_bracket_end = sim_out.rfind("]") + # find the last [ in the out + last_bracket_start = sim_out.rfind("[") + # check if the last bracket pair is "[]", containing no element + if (last_bracket_end - last_bracket_start) == 1: + return True + else: + return False diff --git a/autoline/TB_autoline.py b/autoline/TB_autoline.py new file mode 100644 index 0000000..f23bc32 --- /dev/null +++ b/autoline/TB_autoline.py @@ -0,0 +1,537 @@ +""" +Description : The main function of autoline, originally the first part of autoline.py in AutoBench 1.0 +Author : Ruidi Qiu (r.qiu@tum.de) +Time : 2024/7/24 11:44:15 +LastEdited : 2024/9/1 10:32:18 +""" +import os +import analyze as al +import loader_saver as ls + +import time + +from config import Config +from loader_saver import save_dict_json_form, log_localprefix +from data.probset import HDLBitsProbset +from loader_saver import autologger as logger +from utils.utils import Timer +from autoline.TB1_gen import TaskTBgen +from autoline.TB2_syncheck import TaskTBsim +from autoline.TB3_funccheck import TaskTBcheck +from autoline.TB4_eval import TaskTBeval +from prompt_scripts import BaseScript +from LLM_call import llm_manager + +# [新增] 引入我们刚写的模块 +from autoline.TB_cga import TaskTBCGA + + +def run_autoline(): + # load config + config = Config() + autoline = AutoLine(config) + autoline() + +class AutoLine(): + """the class of the autoline""" + def __init__(self, config: Config): + self.config = config + self.logger = logger + self.logger.assert_(config.get_item("autoline", "promptscript") is not None, "config.autoline.promptscript is None, please check the config file.") + self.load_data() + # set run info + # self.run_info_path = config.save.root + "Chatbench_RunInfo.json" + self.run_info_path = os.path.join(config.save.root, "Chatbench_RunInfo.json") + self.run_info = [] + self.analyzer_en = (config.autoline.onlyrun is None) or (config.autoline.onlyrun == "TBgensimeval") # only run the analyzer when not in the onlyrun mode (partial run) + + def run(self): + for idx, probdata_single in enumerate(self.probset.data): + task_id = probdata_single["task_id"] + self.logger.info("") + self.logger.info("######################### task %d/%d [%s] #########################" % (idx+1, self.probset.num, task_id)) + # run_info_single = pipeline_one_prob(probdata_single, self.config) + one_task = AutoLine_Task(probdata_single, self.config) + run_info_single = one_task.run() + self.run_info.append(run_info_single) + # save run info: (write to file every iteration and will overwrite the previous one) + save_dict_json_form(self.run_info, self.run_info_path) + if self.analyzer_en: + self.run_analyzer() + + def __call__(self, *args, **kwargs): + return self.run(*args, **kwargs) + + def load_data(self): + cfg_probset = self.config.autoline.probset + self.probset = HDLBitsProbset() + self.probset.load_by_config(cfg_probset) + + def run_analyzer(self): + analyzer = al.Analyzer(self.run_info, self.config.gpt.model) + analyzer.run() + logger.info(analyzer.messages) + + + +class AutoLine_Task(): + def __init__(self, prob_data:dict, config:Config): + # config: + self.config = config + # probdata: + self.prob_data = prob_data + self.main_model = self.config.gpt.model # The main llm model used in the autoline (generation, correction...) + self.task_id = prob_data["task_id"] + self.task_NO = prob_data["task_number"] + self.prob_description = prob_data["description"] + self.header = prob_data["header"] + self.DUT_golden = prob_data['module_code'] + self.TB_golden = prob_data.get("testbench", None) + self.mutant_list = prob_data.get("mutants", None) + self.rtlgen_list = prob_data.get('llmgen_RTL', None) + self.rtlgen_model = self.config.gpt.rtlgen_model # if llmgen_list is none, this will be used + self.rtl_num = self.config.autoline.TBcheck.rtl_num # will be covered if llmgen_list is not None + # system config: + # self.task_dir = self.config.save.root + self.task_id + "/" + self.task_dir = os.path.join(self.config.save.root, self.task_id) + self.working_dir = self.task_dir + os.makedirs(self.task_dir, exist_ok=True) + # === [CGA Mod] Save DUT immediately to task dir for CGA access === + self.dut_path = os.path.join(self.task_dir, "DUT.v") + ls.save_code(self.DUT_golden, self.dut_path) + # ============================================================== + self.update_desc = config.autoline.update_desc + self.error_interuption = config.autoline.error_interruption # for debug' + self.save_codes = config.autoline.save_finalcodes + self.save_compile = self.config.autoline.save_compile # save the compiling codes in TBcheck and TBeval or not. + # TBgen paras: + self.TBgen_prompt_script = config.autoline.promptscript + self.circuit_type = None + self.scenario_dict = None + self.scenario_num = None + self.checklist_worked = None + # TBcheck paras: + self.TBcheck_correct_max = self.config.autoline.TBcheck.correct_max + self.iter_max = config.autoline.itermax + self.discrim_mode = config.autoline.TBcheck.discrim_mode + self.correct_mode = config.autoline.TBcheck.correct_mode + self.rtl_compens_en = config.autoline.TBcheck.rtl_compens_en + self.rtl_compens_max_iter = config.autoline.TBcheck.rtl_compens_max_iter + self.cga_enabled = config.autoline.cga.enabled + # stages: + self.TBgen_manager:TaskTBgen = None + self.TBgen:BaseScript = None + self.TBsim:TaskTBsim = None + self.TBcheck:TaskTBcheck = None + self.TBeval:TaskTBeval = None + self.stage_now = "initialization" + # changing paras: + self.autoline_iter_now = 0 + self.TB_code_v = None + self.TB_code_py = None + self.next_action = None + # results: + self.incomplete_running = True + self.full_pass = False + self.TB_corrected = False + self.run_info = {} + self.run_info_short = {} + self.TBcheck_rtl_newly_gen_num = 0 # in autoline, "funccheck" = "TBcheck" + self.op_record = [] # will record the order of each stage, for example: ["gen", "syncheck", "funccheck", "gen", "syncheck", "funccheck", "eval"] + self.funccheck_op_record = [] + self.funccheck_iters = [] + #初始化 + self.cga_coverage = 0.0 + # === [CGA Mod] Initialize result dictionary for final reporting === + self.result_dict = { + "task_id": self.task_id, + "stage": "Init", + "pass": False, + "coverage": 0.0, + "cga_enabled": self.cga_enabled + } + # ================================================================= + # renew current section of llm_manager and logger + llm_manager.new_section() + logger.set_temp_log() + + def run(self): + """ + The main function of running the autoline for one problem + """ + with log_localprefix(self.task_id): + self.run_stages() + self.runinfo_update() + if self.save_codes: + self.save_TB_codes() + # === [CGA Mod] Save Result JSON for Analyzer === + self.result_dict['stage'] = self.stage_now + + try: + result_save_path = self.config.autoline.result_path + except AttributeError: + # 如果 config 对象没这个属性,或者它是字典且没这个key + result_save_path = "results" + + # 确保是绝对路径或相对于项目根目录 + if not os.path.exists(result_save_path): + os.makedirs(result_save_path, exist_ok=True) + ls.save_dict_json_form(self.result_dict, os.path.join(result_save_path, f"{self.task_id}.json")) + # =============================================== + return self.run_info + + def run_TBgen(self, subdir:str=None): + # TODO: export the circuit type and scenario number + self.op_record.append("gen") + working_dir = os.path.join(self.task_dir, subdir) if subdir is not None else self.task_dir + self.stage_now = "TBgen" + self.TBgen_manager = TaskTBgen(self.prob_data, self.TBgen_prompt_script, working_dir, self.config) + self.TBgen = self.TBgen_manager.workflow + with log_localprefix("TBgen"): + self.TBgen() + self.TB_code_v = self.TBgen.get_attr("TB_code_v") + self.TB_code_py = self.TBgen.get_attr("TB_code_py") + self.scenario_dict = self.TBgen.get_attr("scenario_dict") + self.scenario_num = self.TBgen.get_attr("scenario_num") + self.circuit_type = self.TBgen.get_attr("circuit_type") + self.checklist_worked = self.TBgen.get_attr("checklist_worked") + self.incomplete_running = True + self._blank_log() + + def run_TBsim(self, subdir:str=None): + self.op_record.append("syncheck") + working_dir = os.path.join(self.task_dir, subdir) if subdir is not None else self.task_dir + self.stage_now = "TBsim" + self.TBsim = TaskTBsim( + self.TBgen, + self.TBgen.TB_code, + self.header, + working_dir, + self.task_id, + self.config + ) + self.TBsim.run() + self.TB_code_v = self.TBsim.TB_code_now + self.TB_code_py = self.TBsim.PY_code_now + self._blank_log() + + def run_TBcheck(self, subdir:str=None): + self.op_record.append("funccheck") + working_dir = os.path.join(self.task_dir, subdir) if subdir is not None else self.task_dir + self.stage_now = "TBcheck" + self.TBcheck = TaskTBcheck( + task_dir = working_dir, + task_id = self.task_id, + description = self.prob_description, + module_header = self.header, + TB_code_v = self.TB_code_v, + TB_code_py = self.TB_code_py, + rtl_list = self.rtlgen_list, + rtl_num = self.rtl_num, + scenario_num = self.scenario_num, + correct_max = self.TBcheck_correct_max, + runfiles_save=self.save_compile, + discriminator_mode=self.discrim_mode, + corrector_mode=self.correct_mode, + circuit_type=self.circuit_type, + rtl_compens_en=self.rtl_compens_en, + rtl_compens_max_iter=self.rtl_compens_max_iter, + main_model = self.main_model, + rtlgen_model = self.rtlgen_model, + desc_improve=self.update_desc + ) + self.rtlgen_list = self.TBcheck.rtl_list + self.TBcheck.run() + self.TB_code_v = self.TBcheck.TB_code_v + self.TB_code_py = self.TBcheck.TB_code_py + self.TB_corrected = self.TBcheck.corrected + self.funccheck_op_record.append(self.TBcheck.op_record) + self.funccheck_iters.append(self.TBcheck.iter_now) + self.TBcheck_rtl_newly_gen_num += self.TBcheck.rtl_newly_gen_num + self.next_action = self.TBcheck.next_action + if self.update_desc: + self.prob_data['description'] = self.TBcheck.update_description() + self.prob_description = self.prob_data['description'] + self._blank_log() + + def run_TBeval(self, subdir:str=None): + self.op_record.append("eval") + working_dir = os.path.join(self.task_dir, subdir) if subdir is not None else self.task_dir + self.stage_now = "TBeval" + self.TBeval = TaskTBeval( + self.task_id, + working_dir, + TB_gen=self.TB_code_v, + TB_golden=self.TB_golden, + DUT_golden=self.DUT_golden, + DUT_mutant_list=self.mutant_list, + DUT_gptgen_list=None, + pychecker_en=self.TBsim.pychecker_en, + pychecker_code=self.TB_code_py, + runfiles_save=self.save_compile + ) + # attention: the rtls in DUT_gptgen_list are not the same as the rtls used in TBcheck, so currently we just block this feature + try: + self.TBeval.run() + except: + logger.failed("error when running TBeval, the autoline for this task stopped.") + self.incomplete_running = True + self._blank_log() + # 在 run_TB4_eval 或其他方法旁边添加这个新方法 + def run_TBCGA(self, work_subdir="CGA", optimize=True, op_name="cga"): + """ + Coverage-Guided Agent 阶段 + """ + self.stage_now = "TBCGA" + self.op_record.append(op_name) + + cga = TaskTBCGA( + task_dir=self.task_dir, + task_id=self.task_id, + header=self.header, + DUT_code=self.DUT_golden, + TB_code=self.TB_code_v, + config=self.config, + work_subdir=work_subdir, + max_iter=(self.config.autoline.cga.max_iter if optimize else 0) + ) + + # [修改] 接收分数 + final_tb, final_score = cga.run() + + + self.cga_coverage = final_score + # 更新状态 + self.TB_code_v = final_tb + self.result_dict['coverage'] = final_score + + # [新增] 强制归档 final_TB.v 到工作目录 + final_tb_path = os.path.join(self.task_dir, "final_TB.v") + ls.save_code(final_tb, final_tb_path) + logger.info(f"Saved optimized TB to: {final_tb_path}") + + def run_stages(self): + with Timer(print_en=False) as self.running_time: + if not self.error_interuption: + self.run_stages_core() + else: + try: + self.run_stages_core() + except Exception as e: + self.incomplete_running = True + logger.error("error when running %s, the autoline for this task stopped. error message: %s"%(self.stage_now, str(e))) + if self.error_interuption: + # if True, stop the pipeline + raise e + self.incomplete_running = False + + def run_stages_core(self): + match self.config.autoline.onlyrun: + case "TBgen": + self.run_TBgen() + case "TBgensim": + self.run_TBgen() + self.run_TBsim() + # case _: # default, run all + case "TBgensimeval": + try: + self.run_TBgen("1_TBgen") + self.run_TBsim("2_TBsim") + self.run_TBeval("3_TBeval") + except Exception as e: + self.incomplete_running = True + logger.error("error when running %s, the autoline for this task stopped. error message: %s"%(self.stage_now, str(e))) + else: + self.incomplete_running = False + case _: # default, run all + for i in range(self.iter_max): + self.autoline_iter_now = i + try: + self.run_TBgen(f"{i+1}_1_TBgen") + self.run_TBsim(f"{i+1}_2_TBsim") + self.run_TBcheck(f"{i+1}_3_TBcheck") + except Exception as e: + + + # logger.error(f"error when running {self.stage_now}, current pipeline iter: {i+1}, will {"REBOOT" if i= self.target_coverage: +# # logger.success(f"Target reached at baseline!") +# # # [修改] 返回元组 (代码, 分数) +# # return self.best_tb, self.best_score + +# # # --- Loop --- +# # for i in range(1, self.max_iter + 1): +# # logger.info(f"--- CGA Iter {i} / {self.max_iter} ---") + +# # # === [新增] 能量检查:是否还有活跃目标 === +# # if self.energy_allocator: +# # current_target = self.energy_allocator.select_next_target() +# # if not current_target: +# # logger.info("No more active targets with remaining energy. Stopping.") +# # break +# # logger.info(f"Target: {current_target}") +# # # ========================================= + +# # if not last_annotated_file: break + +# # # [修改] 传递语义分析结果、能量分配器、多样性注入器给 CoverageParser +# # parser = CoverageParser( +# # last_annotated_file, +# # tb_code=self.best_tb, +# # semantic_result=self.semantic_result, +# # energy_allocator=self.energy_allocator, +# # diversity_injector=self.diversity_injector # [新增] +# # ) +# # prompt = parser.generate_prompt(self.best_score) + +# # if not prompt: +# # logger.info("No reachable missing blocks found. Stopping.") +# # break + +# # logger.info(f"Asking LLM to fix missing logic (Current: {self.best_score:.2f}%)...") +# # messages = [{"role": "user", "content": prompt}] + +# # try: +# # response, _ = llm.llm_call(messages, self.model) +# # codes = llm.extract_code(response, "verilog") +# # new_task_code = codes[0] if codes else "" +# # if not new_task_code: +# # # [新增] 记录失败 +# # if self.energy_allocator: +# # self.energy_allocator.record_generation( +# # success=False, +# # coverage_delta=0.0, +# # energy_cost=1.0 +# # ) +# # continue +# # except Exception as e: +# # logger.error(f"LLM Call failed: {e}") +# # # [新增] 记录失败 +# # if self.energy_allocator: +# # self.energy_allocator.record_generation( +# # success=False, +# # coverage_delta=0.0, +# # energy_cost=1.0 +# # ) +# # break + +# # injector = TBInjector(self.best_tb) +# # enhanced_tb = injector.inject(new_task_code, iter_idx=i) + +# # iter_dir = os.path.join(work_dir, f"iter_{i}") +# # os.makedirs(iter_dir, exist_ok=True) + +# # self._prepare_dut(iter_dir) +# # ls.save_code(enhanced_tb, os.path.join(iter_dir, "driver.v")) + +# # success, new_score, new_annotated_path = verilator_run_coverage(iter_dir, "DUT.v", "driver.v") + +# # # === [新增] 记录生成结果到能量分配器 === +# # coverage_delta = new_score - self.best_score if success else 0.0 +# # generation_success = success and new_score > self.best_score + +# # if self.energy_allocator: +# # self.energy_allocator.record_generation( +# # success=generation_success, +# # coverage_delta=coverage_delta, +# # energy_cost=1.0 +# # ) +# # # ========================================= + +# # # === [新增] 记录测试用例到多样性历史 === +# # if self.diversity_injector: +# # # 提取已知信号 +# # known_signals = [] +# # if self.semantic_result: +# # known_signals = [p.get('name', '') for p in self.semantic_result.get('ports', [])] + +# # self.diversity_injector.record_test( +# # code=new_task_code, +# # target_function=self.energy_allocator.current_target.function_point if self.energy_allocator and self.energy_allocator.current_target else "", +# # coverage_score=new_score, +# # success=generation_success, +# # iteration=i, +# # known_signals=known_signals +# # ) +# # # ======================================= + +# # if success and new_score > self.best_score: +# # improvement = new_score - self.best_score +# # logger.success(f"Coverage Improved! +{improvement:.2f}% ({self.best_score:.2f}% -> {new_score:.2f}%)") +# # self.best_score = new_score +# # self.best_tb = enhanced_tb +# # last_annotated_file = new_annotated_path +# # elif success and new_score == self.best_score: +# # logger.info(f"Coverage unchanged. Keeping previous.") +# # else: +# # logger.warning(f"Regression or Failure. Discarding changes.") + +# # if self.best_score >= self.target_coverage: +# # logger.success("Target coverage reached!") +# # break + +# # logger.info(f"CGA Finished. Final Coverage: {self.best_score:.2f}%") + +# # # === [新增] 生成能量分配报告 === +# # if self.energy_allocator: +# # energy_report = self.energy_allocator.generate_report() +# # ls.save_code(energy_report, os.path.join(work_dir, "energy_report.txt")) +# # logger.info(f"Energy report saved to {work_dir}/energy_report.txt") +# # # ================================= + +# # # === [新增] 生成多样性报告并保存历史 === +# # if self.diversity_injector: +# # diversity_report = self.diversity_injector.generate_diversity_report() +# # ls.save_code(diversity_report, os.path.join(work_dir, "diversity_report.txt")) +# # logger.info(f"Diversity report saved to {work_dir}/diversity_report.txt") + +# # # 保存测试历史 +# # self.diversity_injector.history.save() +# # # ====================================== + +# # # [修改] 返回元组 (代码, 分数) +# # return self.best_tb, self.best_score + +# #终版 +# """ +# Description : Coverage-Guided Agent (CGA) Main Controller +# - Integrated with Layer 0: Semantic Analysis +# - Integrated with Layer 1: Diversity Constraint Injection +# - Integrated with Layer 3: Quality Evaluation +# - Integrated with Layer 4: Energy Allocation +# Author : CorrectBench Integration +# """ +# import os +# import sys +# import shutil + +# import LLM_call as llm +# import loader_saver as ls +# from loader_saver import autologger as logger +# from utils.verilator_call import verilator_run_coverage +# from autoline.cga_utils import CoverageParser, TBInjector +# # [新增] 导入语义分析层 +# from autoline.semantic_analyzer import SemanticAnalyzer, FunctionPointType +# # [新增] 导入能量分配层 +# from autoline.energy_allocator import EnergyAllocator, EnergyState +# # [新增] 导入多样性约束注入器 +# from autoline.diversity_injector import DiversityInjector +# # [新增] 导入测试历史管理器 +# from autoline.test_history import TestHistoryManager +# # [新增] 导入质量评估层 +# from autoline.quality_evaluator import QualityEvaluator, DiversityScore, SemanticCoverageResult + +# class TaskTBCGA: +# def __init__(self, task_dir, task_id, header, DUT_code, TB_code, config): +# self.task_dir = task_dir +# self.task_id = task_id +# self.header = header +# self.DUT_code = DUT_code +# self.TB_code = TB_code +# self.config = config + +# self.max_iter = 5 +# self.target_coverage = 95.0 +# self.model = config.gpt.model + +# self.best_tb = TB_code +# self.best_score = 0.0 + +# # [新增] 能量分配器 +# self.energy_allocator: EnergyAllocator = None +# # [新增] 多样性约束注入器 +# self.diversity_injector: DiversityInjector = None +# # [新增] 质量评估器 +# self.quality_evaluator: QualityEvaluator = None + +# # [新增辅助函数] 从父目录拷贝 DUT +# def _prepare_dut(self, target_dir): +# source_dut = os.path.join(self.task_dir, "DUT.v") +# target_dut = os.path.join(target_dir, "DUT.v") + +# # 优先拷贝现有的文件 +# if os.path.exists(source_dut): +# shutil.copy(source_dut, target_dut) +# else: +# # 只有当文件由于某种原因被删除了,才降级使用内存中的 code +# ls.save_code(self.DUT_code, target_dut) + +# def _generate_exploration_prompt(self, iteration: int) -> str: +# """ +# 生成探索性测试 Prompt + +# 当找不到明确的 missing blocks 但覆盖率仍未达标时, +# 生成一个探索性 Prompt 来尝试发现新的测试路径。 + +# Args: +# iteration: 当前迭代次数 + +# Returns: +# 探索性测试 Prompt,如果无法生成则返回 None +# """ +# # 从语义分析结果获取 FSM 和功能点信息 +# fsm_info = "" +# if self.semantic_result: +# fsm_data = self.semantic_result.get('fsm', {}) +# if fsm_data: +# states = fsm_data.get('states', []) +# state_var = fsm_data.get('state_variable', 'state') +# fsm_info = f""" +# [FSM INFORMATION] +# - State variable: {state_var} +# - Known states: {', '.join(states) if states else 'unknown'} + +# The DUT appears to be a Finite State Machine. To improve coverage: +# 1. Try to visit each state by driving inputs that trigger state transitions +# 2. For each state, try different input combinations +# 3. Consider edge cases: reset transitions, timeout conditions, error states +# """ + +# # 从能量分配器获取目标功能点 +# target_info = "" +# if self.energy_allocator and self.energy_allocator.current_target: +# target = self.energy_allocator.current_target +# target_info = f""" +# [CURRENT TARGET] +# Focus on: {target.function_point} +# Remaining energy: {target.remaining} +# """ + +# # 从多样性注入器获取已尝试的测试 +# diversity_hints = "" +# if self.diversity_injector: +# history = self.diversity_injector.history +# # if history and len(history.history) > 0: +# # recent_tests = history.history[-5:] if len(history.history) > 5 else history.history +# if history and hasattr(history, 'records') and len(history.records) > 0: +# recent_tests = history.records[-5:] if len(history.records) > 5 else history.records +# diversity_hints = f""" +# [RECENTLY TRIED APPROACHES - AVOID REPETITION] +# Recent test patterns tried: +# """ +# # for i, test in enumerate(recent_tests): +# # diversity_hints += f"- Iter {test.get('iteration', i)}: target={test.get('target_function', 'unknown')}\n" +# for i, test in enumerate(recent_tests): +# # TestRecord 是 dataclass,使用属性访问 +# target = getattr(test, 'target_function', 'unknown') if hasattr(test, 'target_function') else 'unknown' +# iteration = getattr(test, 'iteration', i) if hasattr(test, 'iteration') else i +# diversity_hints += f"- Iter {iteration}: target={target}\n" + +# prompt = f""" +# [EXPLORATION MODE - ITERATION {iteration}] + +# Current coverage is {self.best_score:.2f}%, but no specific uncovered code blocks were identified. +# This may happen when: +# 1. Coverage data is incomplete or filtered +# 2. Branch/condition coverage needs improvement (not just line coverage) +# 3. State transitions in FSM are not fully exercised + +# {fsm_info} +# {target_info} +# {diversity_hints} + +# [YOUR TASK] +# Write an EXPLORATORY test scenario that: +# 1. Covers different input combinations than previous tests +# 2. Explores different FSM state transitions +# 3. Tests edge cases and boundary conditions +# 4. Varies timing and sequence of inputs + +# [OUTPUT FORMAT] +# Return ONLY Verilog test scenario code (no task wrapper). +# Use the signal names from the testbench. + +# ```verilog +# // Your exploratory test code here +# ``` +# """ +# return prompt + + + + +# def run(self): +# logger.info(f"[{self.task_id}] Starting Coverage-Guided Agent (CGA)...") + +# # 1. 确保工作目录存在 (saves/任务名/5_CGA) +# work_dir = os.path.join(self.task_dir, "5_CGA") +# if os.path.exists(work_dir): +# shutil.rmtree(work_dir) +# os.makedirs(work_dir, exist_ok=True) + +# # === [新增] Step 0: 语义分析 === +# logger.info(f"[{self.task_id}] Running Semantic Analysis (Layer 0)...") +# self.semantic_result = None +# try: +# semantic_analyzer = SemanticAnalyzer(self.DUT_code) +# self.semantic_result = semantic_analyzer.analyze() + +# # 记录分析结果摘要 +# fp_count = len(self.semantic_result.get('function_points', [])) +# fsm_info = semantic_analyzer.get_fsm_info() +# if fsm_info: +# logger.info(f" FSM detected: {fsm_info.get('state_variable', 'unknown')} " +# f"({len(fsm_info.get('states', []))} states)") +# logger.info(f" Total function points identified: {fp_count}") + +# # 保存语义分析报告 +# semantic_report = semantic_analyzer.generate_prompt_context() +# ls.save_code(semantic_report, os.path.join(work_dir, "semantic_analysis.txt")) + +# # === [新增] Step 0.1: 初始化能量分配器 === +# if self.semantic_result.get('function_points'): +# self.energy_allocator = EnergyAllocator(max_iterations=self.max_iter) +# energy_init_result = self.energy_allocator.initialize( +# self.semantic_result['function_points'] +# ) +# logger.info(f" Energy allocator initialized: {energy_init_result['targets']} targets") + +# # === [新增] Step 0.2: 初始化多样性约束注入器 === +# history_file = os.path.join(work_dir, "test_history.json") +# # 创建 TestHistoryManager 并传递 history_file +# history_manager = TestHistoryManager(history_file=history_file) +# self.diversity_injector = DiversityInjector(history_manager=history_manager) +# logger.info(f" Diversity injector initialized with history file: {history_file}") + +# # === [新增] Step 0.3: 初始化质量评估器 === +# if self.semantic_result.get('function_points'): +# self.quality_evaluator = QualityEvaluator( +# function_points=self.semantic_result['function_points'] +# ) +# logger.info(f" Quality evaluator initialized") + +# except Exception as e: +# logger.warning(f"Semantic analysis failed: {e}. Continuing without semantic guidance.") +# # ================================ + +# current_tb = self.TB_code +# last_annotated_file = None + +# # --- Baseline --- +# logger.info(f"--- CGA Iter 0 (Baseline) ---") +# iter0_dir = os.path.join(work_dir, "iter_0") +# os.makedirs(iter0_dir, exist_ok=True) + +# self._prepare_dut(iter0_dir) +# ls.save_code(current_tb, os.path.join(iter0_dir, "driver.v")) + +# success, score, annotated_path = verilator_run_coverage(iter0_dir, "DUT.v", "driver.v") + +# self.best_score = score +# self.best_tb = current_tb +# last_annotated_file = annotated_path + +# logger.info(f"Baseline Coverage: {score:.2f}%") + +# if score >= self.target_coverage: +# logger.success(f"Target reached at baseline!") +# # [修改] 返回元组 (代码, 分数) +# return self.best_tb, self.best_score + +# # --- Loop --- +# for i in range(1, self.max_iter + 1): +# logger.info(f"--- CGA Iter {i} / {self.max_iter} ---") + +# # === [新增] 能量检查:是否还有活跃目标 === +# if self.energy_allocator: +# current_target = self.energy_allocator.select_next_target() +# if not current_target: +# logger.info("No more active targets with remaining energy. Stopping.") +# break +# logger.info(f"Target: {current_target}") +# # ========================================= + +# if not last_annotated_file: break + +# # [修改] 传递语义分析结果、能量分配器、多样性注入器给 CoverageParser +# parser = CoverageParser( +# last_annotated_file, +# tb_code=self.best_tb, +# semantic_result=self.semantic_result, +# energy_allocator=self.energy_allocator, +# diversity_injector=self.diversity_injector # [新增] +# ) +# prompt = parser.generate_prompt(self.best_score) + +# # if not prompt: +# # logger.info("No reachable missing blocks found. Stopping.") +# # break +# if not prompt: +# if self.best_score >= self.target_coverage: +# break # 达标才停止 +# else: +# # 未达标,尝试探索性测试 +# prompt = self._generate_exploration_prompt(i) + +# logger.info(f"Asking LLM to fix missing logic (Current: {self.best_score:.2f}%)...") +# messages = [{"role": "user", "content": prompt}] + +# try: +# response, _ = llm.llm_call(messages, self.model) +# codes = llm.extract_code(response, "verilog") +# new_task_code = codes[0] if codes else "" +# if not new_task_code: +# # [新增] 记录失败 +# if self.energy_allocator: +# self.energy_allocator.record_generation( +# success=False, +# coverage_delta=0.0, +# energy_cost=1.0 +# ) +# continue +# except Exception as e: +# logger.error(f"LLM Call failed: {e}") +# # [新增] 记录失败 +# if self.energy_allocator: +# self.energy_allocator.record_generation( +# success=False, +# coverage_delta=0.0, +# energy_cost=1.0 +# ) +# break + +# injector = TBInjector(self.best_tb) +# enhanced_tb = injector.inject(new_task_code, iter_idx=i) + +# iter_dir = os.path.join(work_dir, f"iter_{i}") +# os.makedirs(iter_dir, exist_ok=True) + +# self._prepare_dut(iter_dir) +# ls.save_code(enhanced_tb, os.path.join(iter_dir, "driver.v")) + +# success, new_score, new_annotated_path = verilator_run_coverage(iter_dir, "DUT.v", "driver.v") + +# # === [新增] 记录生成结果到能量分配器 === +# coverage_delta = new_score - self.best_score if success else 0.0 +# generation_success = success and new_score > self.best_score + +# if self.energy_allocator: +# self.energy_allocator.record_generation( +# success=generation_success, +# coverage_delta=coverage_delta, +# energy_cost=1.0 +# ) +# # ========================================= + +# # === [新增] 记录测试用例到多样性历史 === +# if self.diversity_injector: +# # 提取已知信号 +# known_signals = [] +# if self.semantic_result: +# known_signals = [p.get('name', '') for p in self.semantic_result.get('ports', [])] + +# self.diversity_injector.record_test( +# code=new_task_code, +# target_function=self.energy_allocator.current_target.function_point if self.energy_allocator and self.energy_allocator.current_target else "", +# coverage_score=new_score, +# success=generation_success, +# iteration=i, +# known_signals=known_signals +# ) +# # ======================================= + +# # === [新增] Layer 3: 质量评估 === +# if self.quality_evaluator: +# # 评估测试用例质量 +# eval_result = self.quality_evaluator.evaluate_test_case( +# code=new_task_code, +# covered_lines=set(), # 如果有具体覆盖行信息可传入 +# covered_functions=[], # 如果有覆盖功能点信息可传入 +# test_id=f"iter_{i}", +# iteration=i +# ) + +# # 记录多样性得分 +# diversity_score = eval_result.get('diversity', {}).get('overall_score', 0) +# logger.info(f" Quality Evaluation: diversity={diversity_score:.2f}") + +# # 检查是否应该接受该测试用例 +# should_accept, reason = self.quality_evaluator.should_accept(eval_result) +# if not should_accept: +# logger.warning(f" Quality check failed: {reason}") +# # ===================================== + +# if success and new_score > self.best_score: +# improvement = new_score - self.best_score +# logger.success(f"Coverage Improved! +{improvement:.2f}% ({self.best_score:.2f}% -> {new_score:.2f}%)") +# self.best_score = new_score +# self.best_tb = enhanced_tb +# last_annotated_file = new_annotated_path +# elif success and new_score == self.best_score: +# logger.info(f"Coverage unchanged. Keeping previous.") +# else: +# logger.warning(f"Regression or Failure. Discarding changes.") + +# if self.best_score >= self.target_coverage: +# logger.success("Target coverage reached!") +# break + +# logger.info(f"CGA Finished. Final Coverage: {self.best_score:.2f}%") + +# # === [新增] 生成能量分配报告 === +# if self.energy_allocator: +# energy_report = self.energy_allocator.generate_report() +# ls.save_code(energy_report, os.path.join(work_dir, "energy_report.txt")) +# logger.info(f"Energy report saved to {work_dir}/energy_report.txt") +# # ================================= + +# # === [新增] 生成多样性报告并保存历史 === +# if self.diversity_injector: +# diversity_report = self.diversity_injector.generate_diversity_report() +# ls.save_code(diversity_report, os.path.join(work_dir, "diversity_report.txt")) +# logger.info(f"Diversity report saved to {work_dir}/diversity_report.txt") + +# # 保存测试历史 +# self.diversity_injector.history.save() +# # ====================================== + +# # === [新增] Layer 3: 生成质量评估报告 === +# if self.quality_evaluator: +# quality_report = self.quality_evaluator.generate_report() +# ls.save_code(quality_report, os.path.join(work_dir, "quality_evaluation_report.txt")) +# logger.info(f"Quality evaluation report saved to {work_dir}/quality_evaluation_report.txt") + +# # 输出语义覆盖率摘要 +# coverage_result = self.quality_evaluator.semantic_coverage.calculate_coverage() +# logger.info(f"Semantic Coverage: {coverage_result.semantic_coverage:.2%}") +# # =========================================== + +# # [修改] 返回元组 (代码, 分数) +# return self.best_tb, self.best_score + +""" +Description : Coverage-Guided Agent (CGA) Main Controller + - Integrated with Layer 0: Semantic Analysis + - Integrated with Layer 1: Diversity Constraint Injection + - Integrated with Layer 3: Quality Evaluation + - Integrated with Layer 4: Energy Allocation +Author : CorrectBench Integration +""" +import os +import re +import sys +import shutil + +import LLM_call as llm +import loader_saver as ls +from loader_saver import autologger as logger +from utils.verilator_call import verilator_run_coverage +from autoline.cga_utils import CoverageParser, TBInjector +# [新增] 导入语义分析层 +from autoline.semantic_analyzer import SemanticAnalyzer, FunctionPointType +# [新增] 导入能量分配层 +from autoline.energy_allocator import EnergyAllocator, EnergyState +# [新增] 导入多样性约束注入器 +from autoline.diversity_injector import DiversityInjector +# [新增] 导入测试历史管理器 +from autoline.test_history import TestHistoryManager +# [新增] 导入质量评估层 +from autoline.quality_evaluator import QualityEvaluator, DiversityScore, SemanticCoverageResult + +class TaskTBCGA: + def __init__(self, task_dir, task_id, header, DUT_code, TB_code, config, work_subdir="CGA", max_iter=None): + self.task_dir = task_dir + self.task_id = task_id + self.header = header + self.DUT_code = DUT_code + self.TB_code = TB_code + self.config = config + self.work_subdir = work_subdir + + self.max_iter = config.autoline.cga.max_iter if max_iter is None else max_iter + self.target_coverage = config.autoline.cga.target_coverage + self.model = config.gpt.model + + self.best_tb = TB_code + self.best_score = 0.0 + self.best_covered_lines = set() + self.best_covered_functions = set() + + # [新增] 能量分配器 + self.energy_allocator: EnergyAllocator = None + # [新增] 多样性约束注入器 + self.diversity_injector: DiversityInjector = None + # [新增] 质量评估器 + self.quality_evaluator: QualityEvaluator = None + + # [新增辅助函数] 从父目录拷贝 DUT + def _prepare_dut(self, target_dir): + source_dut = os.path.join(self.task_dir, "DUT.v") + target_dut = os.path.join(target_dir, "DUT.v") + + # 优先拷贝现有的文件 + if os.path.exists(source_dut): + shutil.copy(source_dut, target_dut) + else: + # 只有当文件由于某种原因被删除了,才降级使用内存中的 code + ls.save_code(self.DUT_code, target_dut) + + def _extract_coverage_snapshot(self, annotated_path): + """ + 从 Verilator annotated DUT 中提取当前已覆盖行和已覆盖功能点。 + """ + snapshot = { + "covered_lines": set(), + "covered_functions": set(), + "coverable_lines": set(), + } + + if not annotated_path or not os.path.exists(annotated_path): + return snapshot + + pct_pattern = re.compile(r"^%(\d+)\s+(.*)$") + tilde_pattern = re.compile(r"^~(\d+)\s+(.*)$") + caret_pattern = re.compile(r"^\^(\d+)\s+(.*)$") + plain_pattern = re.compile(r"^\s*(\d+)\s+(.*)$") + decl_pattern = re.compile(r"^\s*(input|output|inout|wire|reg|logic|parameter|localparam|assign)\b") + + with open(annotated_path, "r", encoding="utf-8", errors="ignore") as f: + for line_no, raw_line in enumerate(f, start=1): + stripped = raw_line.strip() + if not stripped: + continue + + count = None + code_part = None + is_caret = False + + match = pct_pattern.match(stripped) + if match: + count = int(match.group(1)) + code_part = match.group(2).strip() + else: + match = tilde_pattern.match(stripped) + if match: + count = int(match.group(1)) + code_part = match.group(2).strip() + else: + match = caret_pattern.match(stripped) + if match: + is_caret = True + code_part = match.group(2).strip() + else: + match = plain_pattern.match(stripped) + if match: + count = int(match.group(1)) + code_part = match.group(2).strip() + + if code_part is None: + continue + + if "//" in code_part: + code_part = code_part.split("//", 1)[0].strip() + + if not code_part: + continue + if decl_pattern.match(code_part): + continue + if code_part in {"begin", "end", "else", "endmodule", "endcase", ");", "default:"}: + continue + if not any(ch.isalnum() for ch in code_part): + continue + + snapshot["coverable_lines"].add(line_no) + if (count is not None) and (count > 0) and not is_caret: + snapshot["covered_lines"].add(line_no) + + snapshot["covered_functions"] = self._map_lines_to_function_points(snapshot["covered_lines"]) + return snapshot + + def _map_lines_to_function_points(self, covered_lines): + """ + 用功能点 location 与已覆盖行做交集,推断当前已命中的功能点。 + """ + matched = set() + + if not self.semantic_result: + return matched + + for fp in self.semantic_result.get("function_points", []): + location = fp.get("location", {}) + start_line = location.get("start_line", 0) + end_line = location.get("end_line", 0) + if (start_line <= 0) or (end_line <= 0): + continue + + if any(start_line <= line_no <= end_line for line_no in covered_lines): + matched.add(fp.get("name", "")) + + matched.discard("") + return matched + + def _generate_exploration_prompt(self, iteration: int) -> str: + """ + 生成探索性测试 Prompt + + 当找不到明确的 missing blocks 但覆盖率仍未达标时, + 生成一个探索性 Prompt 来尝试发现新的测试路径。 + + Args: + iteration: 当前迭代次数 + + Returns: + 探索性测试 Prompt,如果无法生成则返回 None + """ + # 从语义分析结果获取 FSM 和功能点信息 + fsm_info = "" + if self.semantic_result: + fsm_data = self.semantic_result.get('fsm', {}) + if fsm_data: + states = fsm_data.get('states', []) + state_var = fsm_data.get('state_variable', 'state') + fsm_info = f""" +[FSM INFORMATION] +- State variable: {state_var} +- Known states: {', '.join(states) if states else 'unknown'} + +The DUT appears to be a Finite State Machine. To improve coverage: +1. Try to visit each state by driving inputs that trigger state transitions +2. For each state, try different input combinations +3. Consider edge cases: reset transitions, timeout conditions, error states +""" + + # 从能量分配器获取目标功能点 + target_info = "" + if self.energy_allocator and self.energy_allocator.current_target: + target = self.energy_allocator.current_target + target_info = f""" +[CURRENT TARGET] +Focus on: {target.function_point} +Remaining energy: {target.remaining} +""" + + # 从多样性注入器获取已尝试的测试 + diversity_hints = "" + if self.diversity_injector: + history = self.diversity_injector.history + # [修复] TestHistoryManager 使用 records 属性,不是 history + if history and hasattr(history, 'records') and len(history.records) > 0: + recent_tests = history.records[-5:] if len(history.records) > 5 else history.records + diversity_hints = f""" +[RECENTLY TRIED APPROACHES - AVOID REPETITION] +Recent test patterns tried: +""" + for i, test in enumerate(recent_tests): + # TestRecord 是 dataclass,使用属性访问 + target = getattr(test, 'target_function', 'unknown') if hasattr(test, 'target_function') else test.get('target_function', 'unknown') if isinstance(test, dict) else 'unknown' + iteration = getattr(test, 'iteration', i) if hasattr(test, 'iteration') else test.get('iteration', i) if isinstance(test, dict) else i + diversity_hints += f"- Iter {iteration}: target={target}\n" + + prompt = f""" +[EXPLORATION MODE - ITERATION {iteration}] + +Current coverage is {self.best_score:.2f}%, but no specific uncovered code blocks were identified. +This may happen when: +1. Coverage data is incomplete or filtered +2. Branch/condition coverage needs improvement (not just line coverage) +3. State transitions in FSM are not fully exercised + +{fsm_info} +{target_info} +{diversity_hints} + +[YOUR TASK] +Write an EXPLORATORY test scenario that: +1. Covers different input combinations than previous tests +2. Explores different FSM state transitions +3. Tests edge cases and boundary conditions +4. Varies timing and sequence of inputs + +[OUTPUT FORMAT] +Return ONLY Verilog test scenario code (no task wrapper). +Use the signal names from the testbench. + +```verilog +// Your exploratory test code here +``` +""" + return prompt + + def _generate_syntax_fix_prompt(self, original_code: str, syntax_issues: dict, original_prompt: str) -> str: + """ + 生成语法修正 Prompt,让 LLM 修复检测到的语法问题 + + Args: + original_code: 原始生成的代码 + syntax_issues: 语法检查结果 + original_prompt: 原始 Prompt + + Returns: + 修正 Prompt + """ + issues_text = [] + + for issue in syntax_issues.get('width_mismatch', []): + issues_text.append(f"- {issue['message']}") + if 'suggestion' in issue: + issues_text.append(f" Suggestion: {issue['suggestion']}") + + for issue in syntax_issues.get('logic_issues', []): + issues_text.append(f"- {issue['message']}") + if 'suggestion' in issue: + issues_text.append(f" Suggestion: {issue['suggestion']}") + + for issue in syntax_issues.get('syntax_warnings', []): + if issue['severity'] == 'error': + issues_text.append(f"- ERROR: {issue['message']}") + + prompt = f""" +[SYNTAX FIX REQUEST] + +The previously generated Verilog test code has the following issues: + +{chr(10).join(issues_text)} + +[ORIGINAL CODE] +```verilog +{original_code} +``` + +[YOUR TASK] +Fix the above code to address these issues. Pay special attention to: + +1. **Width Mismatch**: When you want to input a bit sequence (e.g., 01111100) to a single-bit signal: + - WRONG: `{{in}} = 8'b01111100;` (truncates to single bit) + - CORRECT: Use a shift register + ```verilog + reg [7:0] shift_reg; + shift_reg = 8'b01111100; + for (i = 0; i < 8; i = i + 1) begin + in = shift_reg[7]; + shift_reg = shift_reg << 1; + @(posedge clk); + end + ``` + +2. **Single-bit Shift**: Shifting a 1-bit signal has no effect: + - WRONG: `in = in >> 1;` (always results in 0) + - CORRECT: Use a multi-bit shift register as shown above + +[OUTPUT FORMAT] +Return ONLY the corrected Verilog test scenario code: +```verilog +// Your corrected test code here +``` +""" + return prompt + + def _get_compile_error(self, iter_dir: str) -> str: + """ + 获取 Verilator 编译错误日志 + + Args: + iter_dir: 迭代目录 + + Returns: + 错误日志字符串 + """ + error_parts = [] + + # 检查 obj_dir 是否存在 + obj_dir = os.path.join(iter_dir, "obj_dir") + if not os.path.exists(obj_dir): + error_parts.append("obj_dir not created - compilation failed early") + + # 检查可能的日志文件 + log_files = [ + os.path.join(iter_dir, "verilator.log"), + os.path.join(iter_dir, "compile.log"), + os.path.join(obj_dir, "Vtestbench.log"), + ] + + for log_file in log_files: + if os.path.exists(log_file): + try: + with open(log_file, 'r', errors='ignore') as f: + content = f.read() + if content.strip(): + error_parts.append(f"=== {os.path.basename(log_file)} ===") + error_parts.append(content[-2000:]) # 最后 2000 字符 + except Exception: + pass + + # 如果没有找到日志文件,检查目录内容 + if not error_parts: + error_parts.append(f"Directory contents of {iter_dir}:") + try: + for item in os.listdir(iter_dir): + error_parts.append(f" {item}") + except Exception: + pass + + return '\n'.join(error_parts) if error_parts else "Unknown compilation error" + + def _generate_compile_fix_prompt(self, compile_error: str, original_code: str) -> str: + """ + 生成编译错误修正 Prompt + + Args: + compile_error: 编译错误日志 + original_code: 原始代码 + + Returns: + 修正 Prompt + """ + # 截取关键错误信息 + error_lines = compile_error.split('\n') + key_errors = [] + for line in error_lines: + line = line.strip() + if any(kw in line.lower() for kw in ['error', 'syntax', 'fatal', 'undefined', 'illegal']): + key_errors.append(line) + if len(key_errors) > 10: # 最多 10 条关键错误 + break + + prompt = f""" +[COMPILATION ERROR FIX REQUEST] + +The Verilog test code failed to compile with Verilator. Here are the key errors: + +``` +{chr(10).join(key_errors) if key_errors else compile_error[:1000]} +``` + +[ORIGINAL CODE] +```verilog +{original_code[:2000]} // Truncated if too long +``` + +[COMMON VERILOG ISSUES TO CHECK] + +1. **Width mismatch**: Assigning wide values to narrow signals + - Problem: `{{in}} = 8'b01111100;` where `in` is 1-bit + - Fix: Use shift register to input bits one at a time + +2. **Undefined signals**: Using signals that are not declared + - Check spelling of signal names against the testbench + +3. **Syntax errors**: Missing semicolons, mismatched begin/end + - Check all statements end with semicolon + - Ensure all `begin` have matching `end` + +4. **Timescale issues**: Missing timescale directive + - The testbench should have `timescale 1ns / 1ps` + +[YOUR TASK] +Generate a CORRECTED version of the test code that will compile successfully. +Focus on fixing the specific errors shown above. + +[OUTPUT FORMAT] +Return ONLY the corrected Verilog test scenario code: +```verilog +// Your corrected test code here +``` +""" + return prompt + + + + def run(self): + logger.info(f"[{self.task_id}] Starting Coverage-Guided Agent (CGA)...") + + # 1. 确保工作目录存在 (saves/任务名/5_CGA) + work_dir = os.path.join(self.task_dir, self.work_subdir) + if os.path.exists(work_dir): + shutil.rmtree(work_dir) + os.makedirs(work_dir, exist_ok=True) + + # === [新增] Step 0: 语义分析 === + logger.info(f"[{self.task_id}] Running Semantic Analysis (Layer 0)...") + self.semantic_result = None + try: + semantic_analyzer = SemanticAnalyzer(self.DUT_code) + self.semantic_result = semantic_analyzer.analyze() + + # 记录分析结果摘要 + fp_count = len(self.semantic_result.get('function_points', [])) + fsm_info = semantic_analyzer.get_fsm_info() + if fsm_info: + logger.info(f" FSM detected: {fsm_info.get('state_variable', 'unknown')} " + f"({len(fsm_info.get('states', []))} states)") + logger.info(f" Total function points identified: {fp_count}") + + # 保存语义分析报告 + semantic_report = semantic_analyzer.generate_prompt_context() + ls.save_code(semantic_report, os.path.join(work_dir, "semantic_analysis.txt")) + + # === [新增] Step 0.1: 初始化能量分配器 === + if self.semantic_result.get('function_points'): + self.energy_allocator = EnergyAllocator(max_iterations=self.max_iter) + energy_init_result = self.energy_allocator.initialize( + self.semantic_result['function_points'] + ) + logger.info(f" Energy allocator initialized: {energy_init_result['targets']} targets") + + # === [新增] Step 0.2: 初始化多样性约束注入器 === + history_file = os.path.join(work_dir, "test_history.json") + # 创建 TestHistoryManager 并传递 history_file + history_manager = TestHistoryManager(history_file=history_file) + self.diversity_injector = DiversityInjector(history_manager=history_manager) + logger.info(f" Diversity injector initialized with history file: {history_file}") + + # === [新增] Step 0.3: 初始化质量评估器 === + if self.semantic_result.get('function_points'): + self.quality_evaluator = QualityEvaluator( + function_points=self.semantic_result['function_points'] + ) + logger.info(f" Quality evaluator initialized") + + except Exception as e: + logger.warning(f"Semantic analysis failed: {e}. Continuing without semantic guidance.") + # ================================ + + current_tb = self.TB_code + last_annotated_file = None + + # --- Baseline --- + logger.info(f"--- CGA Iter 0 (Baseline) ---") + iter0_dir = os.path.join(work_dir, "iter_0") + os.makedirs(iter0_dir, exist_ok=True) + + self._prepare_dut(iter0_dir) + ls.save_code(current_tb, os.path.join(iter0_dir, "driver.v")) + + success, score, annotated_path = verilator_run_coverage(iter0_dir, "DUT.v", "driver.v") + + self.best_score = score + self.best_tb = current_tb + last_annotated_file = annotated_path + baseline_snapshot = self._extract_coverage_snapshot(annotated_path) + self.best_covered_lines = set(baseline_snapshot["covered_lines"]) + self.best_covered_functions = set(baseline_snapshot["covered_functions"]) + + if self.energy_allocator and self.best_covered_functions: + self.energy_allocator.mark_targets_completed(sorted(self.best_covered_functions)) + + if self.quality_evaluator and self.best_covered_functions: + self.quality_evaluator.semantic_coverage.update_coverage( + covered_lines=self.best_covered_lines, + covered_functions=sorted(self.best_covered_functions), + test_id="iter_0", + iteration=0 + ) + + logger.info(f"Baseline Coverage: {score:.2f}%") + + if score >= self.target_coverage: + logger.success(f"Target reached at baseline!") + # [修改] 返回元组 (代码, 分数) + return self.best_tb, self.best_score + + # --- Loop --- + for i in range(1, self.max_iter + 1): + logger.info(f"--- CGA Iter {i} / {self.max_iter} ---") + iter_dir = os.path.join(work_dir, f"iter_{i}") + os.makedirs(iter_dir, exist_ok=True) + + # === [新增] 能量检查:是否还有活跃目标 === + if self.energy_allocator: + current_target = self.energy_allocator.select_next_target() + if not current_target: + logger.info("No more active targets with remaining energy. Stopping.") + break + logger.info(f"Target: {current_target}") + # ========================================= + + if not last_annotated_file: break + + # [修改] 传递语义分析结果、能量分配器、多样性注入器、DUT代码给 CoverageParser + parser = CoverageParser( + last_annotated_file, + tb_code=self.best_tb, + semantic_result=self.semantic_result, + energy_allocator=self.energy_allocator, + diversity_injector=self.diversity_injector, # [新增] + dut_code=self.DUT_code # [新增] 传递 DUT 代码以提取信号名 + ) + prompt = parser.generate_prompt(self.best_score) + + # [修改] 改进停止条件:即使找不到 missing_blocks,只要覆盖率未达标就继续 + if not prompt: + if self.best_score >= self.target_coverage: + logger.success(f"Target coverage reached: {self.best_score:.2f}%") + break + else: + # 覆盖率未达标但找不到明确的 missing_blocks + # 尝试生成随机探索 Prompt + logger.warning(f"No reachable missing blocks found, but coverage ({self.best_score:.2f}%) < target ({self.target_coverage}%).") + logger.info(f"Attempting random exploration to discover uncovered paths...") + prompt = self._generate_exploration_prompt(i) + + if not prompt: + logger.info("Could not generate exploration prompt. Stopping.") + break + + ls.save_code(prompt, os.path.join(iter_dir, "prompt.txt")) + logger.info(f"Asking LLM to fix missing logic (Current: {self.best_score:.2f}%)...") + messages = [{"role": "user", "content": prompt}] + + try: + response, _ = llm.llm_call(messages, self.model) + ls.save_code(response, os.path.join(iter_dir, "llm_response.txt")) + codes = llm.extract_code(response, "verilog") + new_task_code = codes[0] if codes else "" + if not new_task_code: + # [新增] 记录失败 + if self.energy_allocator: + self.energy_allocator.record_generation( + success=False, + coverage_delta=0.0, + energy_cost=1.0 + ) + continue + except Exception as e: + logger.error(f"LLM Call failed: {e}") + # [新增] 记录失败 + if self.energy_allocator: + self.energy_allocator.record_generation( + success=False, + coverage_delta=0.0, + energy_cost=1.0 + ) + break + + ls.save_code(new_task_code, os.path.join(iter_dir, "generated_scenario.v")) + injector = TBInjector(self.best_tb) + enhanced_tb = injector.inject(new_task_code, iter_idx=i) + + # [新增] 检查语法预检查结果 + validation_result = injector.last_validation_result + syntax_issues = validation_result.get('syntax_check', {}) if validation_result else {} + + if syntax_issues.get('should_retry', False): + logger.warning(f"[CGA-{i}] Syntax issues detected in generated code. Attempting retry...") + # 生成修正后的 Prompt,包含语法问题提示 + retry_prompt = self._generate_syntax_fix_prompt(new_task_code, syntax_issues, prompt) + if retry_prompt: + try: + retry_response, _ = llm.llm_call([{"role": "user", "content": retry_prompt}], self.model) + retry_codes = llm.extract_code(retry_response, "verilog") + if retry_codes: + new_task_code = retry_codes[0] + ls.save_code(retry_prompt, os.path.join(iter_dir, "retry_prompt.txt")) + ls.save_code(retry_response, os.path.join(iter_dir, "retry_response.txt")) + ls.save_code(new_task_code, os.path.join(iter_dir, "generated_scenario_retry.v")) + enhanced_tb = injector.inject(new_task_code, iter_idx=i) + logger.info(f"[CGA-{i}] Retry code generated successfully") + except Exception as e: + logger.warning(f"[CGA-{i}] Retry failed: {e}") + + self._prepare_dut(iter_dir) + ls.save_code(enhanced_tb, os.path.join(iter_dir, "driver.v")) + + success, new_score, new_annotated_path = verilator_run_coverage(iter_dir, "DUT.v", "driver.v") + + # [新增] 编译失败时的错误反馈机制 + if not success: + compile_error = self._get_compile_error(iter_dir) + if compile_error: + logger.error(f"[CGA-{i}] Verilator compilation failed:") + logger.error(compile_error[:500]) # 截断过长的错误信息 + + # 尝试让 LLM 修正编译错误(最多 1 次重试) + if not hasattr(self, '_compile_retry_count'): + self._compile_retry_count = {} + self._compile_retry_count[i] = self._compile_retry_count.get(i, 0) + + if self._compile_retry_count[i] < 1: + logger.info(f"[CGA-{i}] Asking LLM to fix compilation errors...") + fix_prompt = self._generate_compile_fix_prompt(compile_error, new_task_code) + try: + fix_response, _ = llm.llm_call([{"role": "user", "content": fix_prompt}], self.model) + fix_codes = llm.extract_code(fix_response, "verilog") + if fix_codes: + fixed_code = fix_codes[0] + ls.save_code(fix_prompt, os.path.join(iter_dir, "compile_fix_prompt.txt")) + ls.save_code(fix_response, os.path.join(iter_dir, "compile_fix_response.txt")) + ls.save_code(fixed_code, os.path.join(iter_dir, "generated_scenario_compile_fix.v")) + enhanced_tb = injector.inject(fixed_code, iter_idx=i) + ls.save_code(enhanced_tb, os.path.join(iter_dir, "driver.v")) + + # 再次尝试编译 + success, new_score, new_annotated_path = verilator_run_coverage(iter_dir, "DUT.v", "driver.v") + if success: + logger.info(f"[CGA-{i}] Compilation fixed! Score: {new_score:.2f}%") + new_task_code = fixed_code + except Exception as e: + logger.warning(f"[CGA-{i}] Compile fix attempt failed: {e}") + self._compile_retry_count[i] += 1 + + coverage_snapshot = self._extract_coverage_snapshot(new_annotated_path) if success else { + "covered_lines": set(), + "covered_functions": set(), + "coverable_lines": set(), + } + current_covered_lines = set(coverage_snapshot["covered_lines"]) + current_covered_functions = set(coverage_snapshot["covered_functions"]) + newly_covered_lines = current_covered_lines - self.best_covered_lines + newly_covered_functions = current_covered_functions - self.best_covered_functions + + # === [新增] 记录生成结果到能量分配器 === + coverage_delta = new_score - self.best_score if success else 0.0 + current_target_name = None + target_hit = False + if self.energy_allocator and self.energy_allocator.current_target: + current_target_name = self.energy_allocator.current_target.function_point + if current_covered_functions: + target_hit = current_target_name in current_covered_functions + else: + target_hit = success and new_score > self.best_score + generation_success = success and target_hit + + if self.energy_allocator: + self.energy_allocator.record_generation( + success=generation_success, + coverage_delta=coverage_delta, + energy_cost=1.0 + ) + extra_completed_functions = set(newly_covered_functions) + if generation_success and current_target_name: + extra_completed_functions.discard(current_target_name) + if extra_completed_functions: + self.energy_allocator.mark_targets_completed(sorted(extra_completed_functions)) + # ========================================= + + # === [新增] 记录测试用例到多样性历史 === + if self.diversity_injector: + # 提取已知信号 + known_signals = [] + if self.semantic_result: + known_signals = [p.get('name', '') for p in self.semantic_result.get('ports', [])] + + self.diversity_injector.record_test( + code=new_task_code, + target_function=self.energy_allocator.current_target.function_point if self.energy_allocator and self.energy_allocator.current_target else "", + coverage_score=new_score, + success=generation_success, + iteration=i, + known_signals=known_signals + ) + # ======================================= + + # === [新增] Layer 3: 质量评估 === + if self.quality_evaluator: + # 评估测试用例质量 + eval_result = self.quality_evaluator.evaluate_test_case( + code=new_task_code, + covered_lines=newly_covered_lines, + covered_functions=sorted(newly_covered_functions), + test_id=f"iter_{i}", + iteration=i + ) + + # 记录多样性得分 + diversity_score = eval_result.get('diversity', {}).get('overall_score', 0) + logger.info(f" Quality Evaluation: diversity={diversity_score:.2f}") + + # 检查是否应该接受该测试用例 + should_accept, reason = self.quality_evaluator.should_accept(eval_result) + if not should_accept: + logger.warning(f" Quality check failed: {reason}") + # ===================================== + + if success and new_score > self.best_score: + improvement = new_score - self.best_score + logger.success(f"Coverage Improved! +{improvement:.2f}% ({self.best_score:.2f}% -> {new_score:.2f}%)") + self.best_score = new_score + self.best_tb = enhanced_tb + last_annotated_file = new_annotated_path + self.best_covered_lines = current_covered_lines + self.best_covered_functions = current_covered_functions + elif success and new_score == self.best_score: + logger.info(f"Coverage unchanged. Keeping previous.") + else: + logger.warning(f"Regression or Failure. Discarding changes.") + + if self.best_score >= self.target_coverage: + logger.success("Target coverage reached!") + break + + logger.info(f"CGA Finished. Final Coverage: {self.best_score:.2f}%") + + # === [新增] 生成能量分配报告 === + if self.energy_allocator: + energy_report = self.energy_allocator.generate_report() + ls.save_code(energy_report, os.path.join(work_dir, "energy_report.txt")) + logger.info(f"Energy report saved to {work_dir}/energy_report.txt") + # ================================= + + # === [新增] 生成多样性报告并保存历史 === + if self.diversity_injector: + diversity_report = self.diversity_injector.generate_diversity_report() + ls.save_code(diversity_report, os.path.join(work_dir, "diversity_report.txt")) + logger.info(f"Diversity report saved to {work_dir}/diversity_report.txt") + + # 保存测试历史 + self.diversity_injector.history.save() + # ====================================== + + # === [新增] Layer 3: 生成质量评估报告 === + if self.quality_evaluator: + quality_report = self.quality_evaluator.generate_report() + ls.save_code(quality_report, os.path.join(work_dir, "quality_evaluation_report.txt")) + logger.info(f"Quality evaluation report saved to {work_dir}/quality_evaluation_report.txt") + + # 输出语义覆盖率摘要 + coverage_result = self.quality_evaluator.semantic_coverage.calculate_coverage() + logger.info(f"Semantic Coverage: {coverage_result.semantic_coverage:.2%}") + # =========================================== + + # [修改] 返回元组 (代码, 分数) + return self.best_tb, self.best_score diff --git a/autoline/__init__.py b/autoline/__init__.py new file mode 100644 index 0000000..9cab2c4 --- /dev/null +++ b/autoline/__init__.py @@ -0,0 +1,22 @@ +""" +Description : Automatic pipeline of Chatbench: from HDLBits problem to simulation +Author : Ruidi Qiu (r.qiu@tum.de) +Time : 2023/12/7 15:13:00 +LastEdited : 2024/8/16 13:37:31 +autoline.py (c) 2023 +""" + +from autoline.TB_autoline import run_autoline + +from autoline.TB1_gen import TaskTBgen +from autoline.TB2_syncheck import TaskTBsim +from autoline.TB3_funccheck import TaskTBcheck, TB_corrector, TB_discriminator +from autoline.TB4_eval import TaskTBeval + + + +if __name__ == "__main__": + raise RuntimeError("you cannot run autoline.py directly!") + # probset = Probset("data/HDLBits/HDLBits_data.jsonl", "data/HDLBits/HDLBits_data_miniset_mutants.jsonl", "data/HDLBits/HDLBits_circuit_type.jsonl", exclude_tasks=['rule110'], filter_content={'circuit_type': 'SEQ'}) + # print(probset.num) + \ No newline at end of file diff --git a/autoline/__pycache__/TB1_gen.cpython-312.pyc b/autoline/__pycache__/TB1_gen.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..2c3885ba293a86614068b1794cbc465137d4df02 GIT binary patch literal 2225 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 literal 0 HcmV?d00001 diff --git a/autoline/__pycache__/TB3_funccheck.cpython-312.pyc b/autoline/__pycache__/TB3_funccheck.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..bd25984235a82e4493b326563873b293147c242c GIT binary patch literal 59802 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
v# z#s#Kuy*>zceN7Qdqt3Xp+FV9nTo>kPeG~U;J)(pNg(%Stp{@mDcTyW+zY$$Dtc8H- zk&k|@HvF1P6C$eDQWq59O6n21r!wnjjFvXCLJ=`ka>msa0Q9)UOlXZuuN8yoqC58} z<63OTZ#-`joAO!JfEVKIlFum+hQf~4UhNG?CgPL*K8HBgA(U!431yn=DQ+4UU60VJ zm-U-+X+45@!TxD!K6OTw8!4w;eVpJWqj_TEr_94;rc^oS6kRkG z``2t|xCzS&u8WIVCcv0DgZLKi6+sDat5C$diB*aa!?gKm_#jm9h%=^2QRqrehI}RJ zb%7ymjRs~yI5ku%#ZD|OiSTEX@v8iGhG(f51~yIX(a4*$`Lwe<41X{&8%*_uQLSqk zRWj4At(r$c{Bh1i#T9`_5E`N3ikajAQBj)cNyp;UBSslGNrG4KBSnmtGlmsi(uFP& z*U{ry>PFfW#ZBbhOnp^iY79x4#NWmDMDwQ&4AD_(JBdUREv6id;cKC9E#^^dt57yb zUFwrAI&k=?ArnfMbnork<3GIX_(A{fz55UJvQ0HzWQ6YR?N$UwJnEP(V;}nZ_a5~h z=T(y%f&m3_=sj zGIxJ9k5bzF;~2;nIaPb?uS%=v62HRN3Z>fa1<&5p@|xMal&9j24%yS9xH{*%fXqrAvK}E0-%)FT1T}}Omx;qybD6?|))4csXKBdF5?*hc6Ho=&vl?C| z-ns|zmKP@&xkhHE>YVi=CH7m#D8qBs=g4=~D$+}|9w&oceYTTizU!22kZgMO*@nn> z+%`uj^_{Y}6Y?fc#X@zv>}hA!S*S*5b->xY`qovsX1i3q z1D48~TfTAVtwXmeZ#RF}_Z?rd`GD+coXxvluw-?VZcVM}dUxy1t$$pST+?&6R9Ag+g-}>HG>F7D>LNvK&Y{C272g~c;xcuhOjiEQgH^TGJNShBN z>z}>5C%OFS5@%VqQR+XDs#^YL{*8Qz->^{i)JHZ1exShv31?6R$+PAsrIk9A>|OBe zdr(roSkfSuG)T^dnYix!&r8cK)V9vWHP6Uvp1J33{rDp%a%5$F%&rFi9=D^%&i!dq zSx=?yuD!U&Yr9+N=;8Cl!vyuccmU_kIvAV9pCjU%n9V>tYX#dzph>EQ3|S>o`L`eB{!yxf0Y@kK=`HX%nRm=BP#+qxHU zi+Bp7nF=1jpZ?QPU+i5>QYBc!+^A(5-XWl_e_#>pLlz=?hDdYHBIM(iMERunmM!aB88`b|XjV_m&ztXs%zh94%+2 zwOi23_6}U!O)EHVIAz<(e}(&JL?SVBmLg$?>_+XMQS+HS%eI1a9#kmC#c}28bVByo zjn-+$m`F192nGC*9xe2kzym6}Y@7HaxYO>@z%_lbO}iD#r+#(~He~OU5GBP1vQ%}` zc0vtAx}|(Gfl?ItyvY63UiTlIC8>hK8GEY0HPbcYSjx9oOyy?#H*8mPJ>+?5cl5 zeDm6kYxC}8!&6Du#`o=;7)-S_E9b#!lSw-d9XiY|GiN(ex`>Q>hR`!g*c%k3P{A0K zkbg-b4t6F%v3cnx_5YygCxr)5QGO@|vui9IjpHl>M2zGjT@reW|ACU5zvI%|s2EG< zDR1%rz;_Xyquf#}65uLVhO<%FN)!1RdTgP`HhOHQ2VulwAG!MJae^Kv>2Zo4Yz9-e zXHD&oom+v>;f%|6aTH(ipNst`JeF)0i{+u!VX;3d<}9wCa1}q{s(->&{e&y~3Ag;G zT*FVf6+h)Ve#*6eYd5GJ_@mkvFuzBn+x|dveZu^VQB~Jl+ zRX8f_5?8k5E@khJRvouk);{FuxioCGm*%}#wB+W>J6}KY=X|zJBWfUX}r?WyxWy YL>0^Zlm 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 Q_gQ)zmWGEM+3bV;A1*wnWB>pF literal 0 HcmV?d00001 diff --git a/autoline/__pycache__/TB_cga.cpython-312.pyc b/autoline/__pycache__/TB_cga.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a08c1242c99001f9ad347956cb5800a9d3c8164a GIT binary patch literal 35891 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 literal 0 HcmV?d00001 diff --git a/autoline/__pycache__/energy_allocator.cpython-312.pyc b/autoline/__pycache__/energy_allocator.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..c32e492e89a047891eed57c8732653f737b2b7b3 GIT binary patch literal 32657 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 literal 0 HcmV?d00001 diff --git a/autoline/__pycache__/quality_evaluator.cpython-312.pyc b/autoline/__pycache__/quality_evaluator.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..15af3528c03381cd8c8520c2215f0fc8f12be45d GIT binary patch literal 39790 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 WZJ2=31%BxJ)cBdI{EU7Wg#7<>twIw3 literal 0 HcmV?d00001 diff --git a/autoline/__pycache__/semantic_analyzer.cpython-312.pyc b/autoline/__pycache__/semantic_analyzer.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..df0437973e28dcdc295dd6ec7d8c8f1a73af36c0 GIT binary patch literal 44148 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 Tuple[str, Dict]: +# # """验证并转换代码 - 主入口""" +# # self.violations = {'critical': [], 'warning': [], 'info': []} +# # self.transformations = [] + +# # if tb_code: +# # self._extract_signals_from_tb(tb_code) + +# # original_lines = code.strip().split('\n') +# # total_lines = len([l for l in original_lines if l.strip() and not l.strip().startswith('//')]) + +# # # Step 1: 移除非法语句 +# # code = self._transform_forbidden_statements(code) + +# # # Step 2: 转换层次化访问 +# # code = self._transform_hierarchical_access(code) + +# # # Step 3: 智能转换内部信号访问 +# # code = self._smart_transform_internal_signals(code) + +# # # Step 4: 最后清理 +# # code = self._final_cleanup(code) + +# # # 计算质量分数 +# # quality_score = self._calculate_quality_score(total_lines) + +# # # 决定是否需要重试 +# # should_retry = quality_score < 50 or len(self.violations['critical']) > 3 + +# # result = { +# # 'quality_score': quality_score, +# # 'is_valid': len(self.violations['critical']) == 0, +# # 'violations': self.violations, +# # 'transformations': self.transformations, +# # 'should_retry': should_retry, +# # 'allowed_signals': self._get_allowed_signals_info() +# # } + +# # return code.strip(), result + +# # def _extract_signals_from_tb(self, tb_code: str): +# # """从测试平台代码中提取DUT输入输出信号""" +# # dut_match = re.search(r'(\w+)\s+(?:DUT|dut|uut|UUT)\s*\(', tb_code, re.IGNORECASE) +# # if dut_match: +# # start = dut_match.start() +# # bracket_count = 0 +# # end = start +# # for i, char in enumerate(tb_code[start:]): +# # if char == '(': +# # bracket_count += 1 +# # elif char == ')': +# # bracket_count -= 1 +# # if bracket_count == 0: +# # end = start + i + 1 +# # break + +# # dut_instance = tb_code[start:end] +# # port_pattern = r'\.(\w+)\s*\(\s*(\w+)\s*\)' + +# # for match in re.finditer(port_pattern, dut_instance): +# # signal_name = match.group(2) + +# # is_input = re.search(rf'\breg\s+(?:\[[\d:]+\]\s*)?{re.escape(signal_name)}\s*[;,\n]', tb_code) +# # is_output = re.search(rf'\bwire\s+(?:\[[\d:]+\]\s*)?{re.escape(signal_name)}\s*[;,\n]', tb_code) + +# # if is_input and signal_name not in self.dut_inputs: +# # self.dut_inputs.append(signal_name) +# # if is_output and signal_name not in self.dut_outputs: +# # self.dut_outputs.append(signal_name) + +# # # 备用方案:通过reg/wire声明推断 +# # if not self.dut_inputs and not self.dut_outputs: +# # for match in re.finditer(r'\breg\s+(?:\[[\d:]+\]\s*)?(\w+)\s*[;,\n]', tb_code): +# # signal = match.group(1) +# # if signal.lower() not in ['file', 'scenario', 'i', 'j', 'k', 'cnt']: +# # if signal not in self.dut_inputs: +# # self.dut_inputs.append(signal) + +# # for match in re.finditer(r'\bwire\s+(?:\[[\d:]+\]\s*)?(\w+)\s*[;,\n]', tb_code): +# # signal = match.group(1) +# # if signal not in self.dut_outputs: +# # self.dut_outputs.append(signal) + +# # def _transform_forbidden_statements(self, code: str) -> str: +# # """转换非法语句""" +# # for pattern, desc, severity in self.FORBIDDEN_STATEMENTS: +# # matches = list(re.finditer(pattern, code, re.IGNORECASE)) +# # for match in reversed(matches): +# # signal = match.group(1) if match.groups() else 'unknown' +# # self.violations[severity].append(f"{desc}: {signal}") + +# # line_start = code.rfind('\n', 0, match.start()) + 1 +# # line_end = code.find('\n', match.end()) +# # if line_end == -1: +# # line_end = len(code) +# # original_line = code[line_start:line_end] + +# # # 尝试转换 force -> 直接赋值(仅对输入信号) +# # if 'force' in match.group(0).lower() and signal in self.dut_inputs: +# # new_line = re.sub(r'\bforce\s+', '', original_line, flags=re.IGNORECASE) +# # code = code[:line_start] + new_line + code[line_end:] +# # self.transformations.append({ +# # 'type': 'force_to_assign', +# # 'original': original_line.strip(), +# # 'transformed': new_line.strip() +# # }) +# # continue + +# # code = code[:line_start] + '// [BLOCKED] ' + original_line.lstrip() + code[line_end:] +# # self.transformations.append({ +# # 'type': 'blocked', +# # 'original': original_line.strip(), +# # 'reason': desc +# # }) + +# # return code + +# # def _transform_hierarchical_access(self, code: str) -> str: +# # """转换层次化访问(如 DUT.state)""" +# # for match in re.finditer(self.HIERARCHICAL_ACCESS, code): +# # prefix = match.group(1) +# # signal = match.group(2) + +# # if prefix.upper() in ['DUT', 'UUT', 'TOP', 'TB']: +# # if signal not in self.dut_outputs: +# # self.violations['critical'].append(f"层次化访问内部信号: {prefix}.{signal}") + +# # line_start = code.rfind('\n', 0, match.start()) + 1 +# # line_end = code.find('\n', match.end()) +# # if line_end == -1: +# # line_end = len(code) +# # original_line = code[line_start:line_end] +# # code = code[:line_start] + '// [HIERARCHY] ' + original_line.lstrip() + code[line_end:] + +# # return code + +# # def _smart_transform_internal_signals(self, code: str) -> str: +# # """智能转换内部信号访问""" +# # lines = code.split('\n') +# # transformed_lines = [] + +# # for line in lines: +# # stripped = line.strip() + +# # if stripped.startswith('//') or not stripped: +# # transformed_lines.append(line) +# # continue + +# # if (stripped.startswith('#') or stripped.startswith('$') or +# # stripped.startswith('repeat(') or stripped.startswith('@(')): +# # transformed_lines.append(line) +# # continue + +# # detected_signals = self._detect_internal_signals_in_line(stripped) +# # has_critical = detected_signals.get('critical', []) +# # has_warning = detected_signals.get('warning', []) + +# # if not has_critical and not has_warning: +# # transformed_lines.append(line) +# # continue + +# # context = self._analyze_signal_context(stripped, detected_signals) + +# # if context['type'] == 'assignment': +# # transformed_lines.append(f"// [INTERNAL_ASSIGN] Cannot modify internal signal") +# # transformed_lines.append(f"// Original: {stripped}") +# # self.violations['critical'].append(f"尝试修改内部信号: {context['signals']}") +# # elif context['type'] == 'condition': +# # transformed = self._transform_condition(stripped, context) +# # transformed_lines.append(transformed) +# # self.transformations.append({ +# # 'type': 'condition_transform', +# # 'original': stripped, +# # 'transformed': transformed +# # }) +# # elif context['type'] == 'wait_for_state': +# # transformed = self._transform_state_wait(stripped, context) +# # transformed_lines.append(transformed) +# # self.transformations.append({ +# # 'type': 'wait_transform', +# # 'original': stripped, +# # 'transformed': transformed +# # }) +# # else: +# # if has_critical: +# # transformed_lines.append(f"// [WARNING] Contains internal signal reference: {has_critical}") +# # transformed_lines.append(f"// Original: {stripped}") +# # for sig in has_critical: +# # self.violations['warning'].append(f"可疑的内部信号访问: {sig}") +# # else: +# # transformed_lines.append(line) + +# # return '\n'.join(transformed_lines) + +# # def _detect_internal_signals_in_line(self, line: str) -> Dict[str, List[str]]: +# # """检测行中的内部信号""" +# # detected = {'critical': [], 'warning': [], 'info': []} + +# # LEGAL_KEYWORDS = { +# # 'repeat', 'posedge', 'negedge', 'begin', 'end', 'if', 'else', +# # 'while', 'for', 'case', 'default', 'always', 'initial', +# # 'assign', 'wire', 'reg', 'input', 'output', 'inout', +# # 'parameter', 'localparam', 'integer', 'real', 'time', +# # 'clk', 'clock', 'reset', 'rst', 'areset', 'rst_n', +# # 'enable', 'ena', 'valid', 'ready', 'data', 'addr', 'address', +# # 'true', 'false', 'idle', 'wait' +# # } + +# # SYSTEM_FUNCTIONS = {'$display', '$write', '$monitor', '$fopen', '$fclose', +# # '$fdisplay', '$fwrite', '$readmemh', '$readmemb', +# # '$finish', '$stop', '$random', '$time', '$stime'} + +# # for severity, patterns in self.INTERNAL_SIGNAL_PATTERNS.items(): +# # for pattern, name in patterns: +# # matches = re.findall(pattern, line, re.IGNORECASE) +# # if matches: +# # for match in matches: +# # if isinstance(match, tuple): +# # match = match[0] if match[0] else match[1] + +# # match_lower = match.lower() if match else '' + +# # if match_lower in LEGAL_KEYWORDS: +# # continue +# # if match in SYSTEM_FUNCTIONS: +# # continue +# # if match in self.dut_inputs or match in self.dut_outputs: +# # continue +# # if match.startswith('$'): +# # continue + +# # if match and match not in detected[severity]: +# # detected[severity].append(match) + +# # return detected + +# # def _analyze_signal_context(self, line: str, signals: Dict) -> Dict: +# # """分析信号使用上下文""" +# # assign_match = re.search(r'(\w+)\s*(?:=|<=)\s*', line) +# # if assign_match: +# # target = assign_match.group(1) +# # if target in signals.get('critical', []) or target in signals.get('warning', []): +# # return {'type': 'assignment', 'signals': [target], 'line': line} + +# # if re.search(r'wait\s*\([^)]*state', line, re.IGNORECASE): +# # return {'type': 'wait_for_state', 'signals': signals.get('critical', []), 'line': line} + +# # if re.search(r'if\s*\(|while\s*\(|@\s*\(', line): +# # return {'type': 'condition', 'signals': signals.get('critical', []) + signals.get('warning', []), 'line': line} + +# # return {'type': 'other', 'signals': signals.get('critical', []) + signals.get('warning', []), 'line': line} + +# # def _transform_condition(self, line: str, context: Dict) -> str: +# # """转换条件判断语句""" +# # original = line + +# # if 'state' in str(context['signals']): +# # indent = len(line) - len(line.lstrip()) +# # spaces = ' ' * indent + +# # transformed = f"// [TRANSFORMED] Original: {original.strip()}\n" +# # transformed += f"{spaces}// Cannot directly check internal state\n" +# # transformed += f"{spaces}// Alternative: Wait for expected clock cycles\n" +# # transformed += f"{spaces}repeat(5) @(posedge clk); // Adjust cycles as needed" + +# # self.violations['warning'].append(f"条件判断转换: {original.strip()}") +# # return transformed + +# # return f"// [TRANSFORMED] {original}" + +# # def _transform_state_wait(self, line: str, context: Dict) -> str: +# # """转换状态等待语句""" +# # indent = len(line) - len(line.lstrip()) +# # spaces = ' ' * indent + +# # transformed = f"// [TRANSFORMED] Original: {line.strip()}\n" +# # transformed += f"{spaces}// Cannot wait for internal state directly\n" +# # transformed += f"{spaces}// Alternative: Drive inputs and wait for expected cycles\n" +# # transformed += f"{spaces}repeat(10) @(posedge clk); // Adjust based on FSM design" + +# # self.violations['info'].append(f"状态等待转换: {line.strip()}") +# # return transformed + +# # def _final_cleanup(self, code: str) -> str: +# # """最终清理""" +# # lines = code.split('\n') +# # cleaned = [] + +# # for line in lines: +# # stripped = line.strip() + +# # if stripped in ['begin', 'end'] and cleaned: +# # last = cleaned[-1].strip() +# # if last.startswith('// [TRANSFORMED]') or last.startswith('// [INTERNAL'): +# # continue + +# # cleaned.append(line) + +# # result = '\n'.join(cleaned) +# # result = re.sub(r'\n\s*\n\s*\n', '\n\n', result) + +# # return result + +# # def _calculate_quality_score(self, total_lines: int) -> int: +# # """计算代码质量分数""" +# # if total_lines == 0: +# # return 0 + +# # score = 100 +# # score -= len(self.violations['critical']) * 20 +# # score -= len(self.violations['warning']) * 5 +# # score += len([t for t in self.transformations if 'blocked' not in t.get('type', '')]) * 5 + +# # return max(0, min(100, score)) + +# # def _get_allowed_signals_info(self) -> Dict: +# # """获取允许的信号信息""" +# # return { +# # 'inputs': self.dut_inputs, +# # 'outputs': self.dut_outputs, +# # 'all_allowed': self.dut_inputs + self.dut_outputs +# # } + +# # def generate_constraint_prompt(self) -> str: +# # """生成动态约束提示 - 使用实际信号名""" +# # prompt = "\n[SIGNAL CONSTRAINTS - DERIVED FROM YOUR DUT]\n" + +# # # 提取复位信号名(优先使用实际的) +# # reset_signal = self._find_reset_signal() + +# # if self.dut_inputs: +# # prompt += "ALLOWED INPUTS (you CAN drive these):\n" +# # for sig in self.dut_inputs: +# # prompt += f" - {sig}\n" +# # else: +# # prompt += "ALLOWED INPUTS: Check the testbench for actual signal names\n" + +# # if self.dut_outputs: +# # prompt += "\nOUTPUTS (you can READ but NOT write):\n" +# # for sig in self.dut_outputs: +# # prompt += f" - {sig}\n" + +# # prompt += f""" +# # FORBIDDEN ACTIONS: +# # 1. NEVER assign values to internal signals (state, counter, etc.) +# # 2. NEVER use 'force' or 'assign' statements +# # 3. NEVER access signals like DUT.state (hierarchical access) + +# # CORRECT APPROACH: +# # - To reach a specific FSM state: drive inputs and WAIT for natural transition +# # - Example: Instead of "state = IDLE", use "{reset_signal} = 1; repeat(2) @(posedge clk); {reset_signal} = 0;" +# # """ +# # return prompt + +# # def _find_reset_signal(self) -> str: +# # """查找复位信号名""" +# # # 按优先级查找常见的复位信号名 +# # reset_candidates = ['areset', 'rst_n', 'rst', 'reset', 'rst_b'] +# # for sig in reset_candidates: +# # if sig in self.dut_inputs: +# # return sig +# # # 如果没找到,检查输入列表中是否有类似名称 +# # for sig in self.dut_inputs: +# # sig_lower = sig.lower() +# # if 'reset' in sig_lower or 'rst' in sig_lower: +# # return sig +# # # 默认返回第一个输入信号(排除 clk) +# # for sig in self.dut_inputs: +# # if 'clk' not in sig.lower(): +# # return sig +# # return "reset" # 兜底 + + +# # # ============================================================================ +# # # CoverageParser - 覆盖率解析器 +# # # ============================================================================ +# # class CoverageParser: +# # """覆盖率解析器 - 从带注释的Verilog文件中提取未覆盖的代码块 + +# # [增强] 集成语义分析结果,提供更精准的 FSM 状态路径指导 +# # [新增] 集成能量分配层,提供目标功能点优先级信息 +# # [新增] 集成多样性约束注入器,避免测试用例同质化 +# # """ + +# # def __init__(self, annotated_file, tb_code=None, semantic_result=None, +# # energy_allocator=None, diversity_injector=None): +# # self.file_path = annotated_file +# # self.tb_code = tb_code +# # self.semantic_result = semantic_result # [新增] 语义分析结果 +# # self.energy_allocator = energy_allocator # [新增] 能量分配器 +# # self.diversity_injector = diversity_injector # [新增] 多样性约束注入器 +# # # 修复:Verilator 覆盖率标记格式为 %NNNNNN 或 ~NNNNNN 或 ^NNNNNN +# # # %NNNNNN - 行覆盖计数(%000000 表示从未执行) +# # # ~NNNNNN - 分支/条件覆盖计数 +# # # ^NNNNNN - 未覆盖的分支 +# # self.line_pattern = re.compile(r'^%(\d+)\s+(.*)$') # 匹配 %NNNNNN code +# # self.tilde_pattern = re.compile(r'^~(\d+)\s+(.*)$') # 匹配 ~NNNNNN code +# # self.caret_pattern = re.compile(r'^\^(\d+)\s+(.*)$') # 匹配 ^NNNNNN code +# # # 有些情况可能是纯数字开头(无前缀) +# # self.plain_pattern = re.compile(r'^\s*(\d+)\s+(.*)$') +# # self.decl_pattern = re.compile(r'^\s*(input|output|inout|wire|reg|logic|parameter|localparam|assign)\b') + +# # self.validator = BlackBoxValidator() +# # if tb_code: +# # self.validator._extract_signals_from_tb(tb_code) + +# # def generate_prompt(self, current_score): +# # """生成覆盖率驱动的Prompt""" +# # if not os.path.exists(self.file_path): +# # return None + +# # try: +# # with open(self.file_path, 'r', encoding='utf-8', errors='ignore') as f: +# # lines = f.readlines() +# # except Exception: +# # return None + +# # missing_blocks = [] +# # current_block = [] +# # recording = False +# # context_buffer = [] +# # CONTEXT_SIZE = 3 + +# # # 收集缺失行用于 FSM 分析 +# # missing_lines = [] + +# # for i, line in enumerate(lines): +# # line = line.strip() +# # count = -1 +# # clean_code = line +# # is_tilde = False +# # is_caret = False + +# # # 尝试匹配各种覆盖率标记格式 +# # match_pct = self.line_pattern.match(line) # %NNNNNN code +# # match_tilde = self.tilde_pattern.match(line) # ~NNNNNN code +# # match_caret = self.caret_pattern.match(line) # ^NNNNNN code +# # match_plain = self.plain_pattern.match(line) # NNNNNN code (无前缀) + +# # if match_pct: +# # count = int(match_pct.group(1)) +# # clean_code = match_pct.group(2).strip() +# # elif match_tilde: +# # count = int(match_tilde.group(1)) +# # clean_code = match_tilde.group(2).strip() +# # is_tilde = True +# # elif match_caret: +# # count = int(match_caret.group(1)) +# # clean_code = match_caret.group(2).strip() +# # is_caret = True +# # elif match_plain: +# # # 纯数字格式(可能出现在某些 Verilator 版本) +# # count = int(match_plain.group(1)) +# # clean_code = match_plain.group(2).strip() + +# # if "//" in clean_code: +# # clean_code = clean_code.split("//")[0].strip() + +# # is_hard_noise = (self.decl_pattern.match(clean_code) or clean_code == "endmodule") +# # is_soft_noise = (len(clean_code) < 2 or clean_code in ["end", "begin", "else", ");", "endcase", "default:"] or +# # clean_code.startswith("module ") or not any(c.isalnum() for c in clean_code)) + +# # # 覆盖状态判断: +# # # - %NNNNNN: count > 0 表示已覆盖,count == 0 表示未覆盖 +# # # - ~NNNNNN: 分支覆盖标记,需要进一步检查 +# # # - ^NNNNNN: 未覆盖分支标记 +# # is_definitely_covered = (not is_tilde and not is_caret and count > 0) +# # is_definitely_missed = (not is_tilde and not is_caret and count == 0 and not is_hard_noise and not is_soft_noise) or \ +# # (is_caret and not is_hard_noise and not is_soft_noise) + +# # if recording: +# # if is_definitely_covered: +# # missing_blocks.append(current_block) +# # missing_lines.extend(current_block) +# # current_block = [] +# # recording = False +# # if not is_hard_noise: +# # context_buffer.append(clean_code) +# # else: +# # if not is_hard_noise and not (is_soft_noise and len(clean_code) < 4): +# # current_block.append(f"Line {i+1}: {clean_code}") +# # else: +# # if is_definitely_missed: +# # recording = True +# # if context_buffer: +# # current_block.append(f"... (Context)") +# # for ctx in context_buffer: +# # current_block.append(f" {ctx}") +# # current_block.append(f"Line {i+1}: {clean_code} <--- MISSING START") +# # else: +# # if not is_hard_noise and not (is_soft_noise and len(clean_code) < 4): +# # context_buffer.append(clean_code) +# # if len(context_buffer) > CONTEXT_SIZE: +# # context_buffer.pop(0) + +# # if recording and current_block: +# # missing_blocks.append(current_block) +# # missing_lines.extend(current_block) +# # if not missing_blocks: +# # return None + +# # selected_blocks = missing_blocks[:50] + +# # # 获取实际信号名用于示例 +# # reset_signal = self.validator._find_reset_signal() +# # inputs_no_clk = [s for s in self.validator.dut_inputs if 'clk' not in s.lower()] +# # example_signal = inputs_no_clk[0] if inputs_no_clk else (reset_signal if reset_signal != "reset" else "ena") + +# # # 分析 FSM 相关的缺失代码 +# # fsm_analysis = self._analyze_fsm_missing(missing_lines) + +# # # [新增] 从语义分析结果获取 FSM 和功能点信息 +# # semantic_context = self._generate_semantic_context() + +# # prompt = f""" +# # [ROLE] +# # You are a hardware verification expert. Your task is to write a test scenario to improve code coverage. + +# # [COVERAGE STATUS] +# # Current testbench achieves {current_score:.2f}% coverage. +# # The following logic blocks in the DUT are NEVER executed during simulation: + +# # """ +# # for idx, block in enumerate(selected_blocks): +# # prompt += f"--- Missing Logic Block {idx+1} ---\n" + "\n".join(block) + "\n\n" + +# # # [新增] 添加语义分析上下文 +# # if semantic_context: +# # prompt += f""" +# # [SEMANTIC ANALYSIS - MODULE UNDERSTANDING] +# # {semantic_context} +# # """ + +# # # === [新增] 添加能量分配目标上下文 === +# # if self.energy_allocator: +# # energy_context = self.energy_allocator.get_target_context() +# # if energy_context: +# # prompt += f""" +# # [ENERGY-ALIGNED TARGET - PRIORITY] +# # {energy_context} +# # Focus your test scenario on covering this high-priority target first. +# # """ +# # # ===================================== + +# # prompt += self.validator.generate_constraint_prompt() + +# # # 添加 FSM 分析提示 +# # if fsm_analysis: +# # prompt += f""" +# # [FSM STATE TRANSITION ANALYSIS - CRITICAL] +# # {fsm_analysis} + +# # IMPORTANT: FSM transitions have PRIORITY ORDER! +# # - 'if' conditions are evaluated TOP to BOTTOM +# # - The FIRST matching condition determines the next state +# # - To trigger a branch like "else if (condition)", you MUST ensure all higher-priority conditions are FALSE +# # - Read the missing code's context carefully: what conditions precede it? + +# # """ + +# # prompt += f""" +# # [OUTPUT REQUIREMENTS - CRITICAL] +# # 1. Return ONLY Verilog test scenario code (NOT a task definition) +# # 2. Your code will be inserted INTO an existing `initial begin ... end` block +# # 3. DO NOT wrap your code in `task ... endtask` - just write the test sequence directly +# # 4. DO NOT use `$finish` or `$stop` - the testbench handles simulation end + +# # [CODING STYLE] +# # 1. Use blocking assignments for input signals: `signal = value;` +# # 2. Use `#N;` for time delays: `#10;` means wait 10 time units +# # 3. Use `repeat(N) @(posedge clk);` to wait for N clock cycles +# # 4. Start with reset sequence if needed + +# # [BLACK-BOX CONSTRAINTS - CRITICAL] +# # 1. You can ONLY control module INPUTS listed above +# # 2. You CANNOT access internal signals (state, next_state, counters, etc.) +# # 3. You CANNOT use `force` or `assign` on internal signals +# # 4. To trigger a specific state: drive inputs and wait for the FSM to reach it naturally + +# # [STEP-BY-STEP APPROACH - REQUIRED] +# # For each missing branch, think through: +# # 1. What STATE must the FSM be in? (Look at the case statement) +# # 2. What CONDITIONS must be true/false? (Check priority order!) +# # 3. How to reach that state from reset? (Trace state transitions) +# # 4. What inputs to apply and in what order? + +# # [POSITIVE EXAMPLE - CORRECT APPROACH] +# # ```verilog +# # // Reset sequence - use ACTUAL input signal names from above +# # {reset_signal} = 1; +# # repeat(2) @(posedge clk); +# # {reset_signal} = 0; + +# # // Wait for FSM to reach desired state (estimate cycles) +# # repeat(3) @(posedge clk); + +# # // Trigger missing branch by driving inputs +# # {example_signal} = 1; +# # repeat(5) @(posedge clk); +# # {example_signal} = 0; +# # repeat(10) @(posedge clk); +# # ``` + +# # [NEGATIVE EXAMPLE - DO NOT DO THIS] +# # ```verilog +# # // WRONG: Using wrong signal name (e.g., 'reset' instead of '{reset_signal}') +# # reset = 1; // ERROR: Signal 'reset' does not exist! Use '{reset_signal}' instead! + +# # // WRONG: Not considering condition priority in FSM +# # // If missing code is "else if (condition_b)", you must make condition_a FALSE first! +# # // Example: if FSM has "if (!signal_a) ... else if (signal_b) ..." +# # // Then signal_a must be 1 (FALSE) for the else-if branch to execute +# # signal_a = 0; // WRONG: This blocks the else-if branch! +# # signal_b = 1; // This will NOT trigger because signal_a=0 took priority + +# # // CORRECT: Analyze priority, set higher-priority conditions to FALSE +# # signal_a = 1; // Now the first condition (!signal_a) is FALSE +# # signal_b = 1; // Now this else-if branch can execute + +# # // WRONG: Trying to assign internal state +# # state = IDLE; // ERROR: Cannot modify internal signal! + +# # // WRONG: Using force on internal signal +# # force DUT.state = WL; // ERROR: Cannot force internal signal! + +# # // WRONG: Checking internal state in condition +# # if (state == WL) begin // ERROR: Cannot read internal signal! +# # {example_signal} = 1; +# # end + +# # // CORRECT ALTERNATIVE: Estimate timing instead +# # repeat(5) @(posedge clk); // Wait for FSM to reach expected state +# # {example_signal} = 1; +# # ``` + +# # [SIGNAL NAME WARNING - CRITICAL] +# # - DO NOT use 'reset' if the actual signal is '{reset_signal}' +# # - DO NOT use 'rst' if the actual signal is '{reset_signal}' +# # - ALWAYS use EXACT signal names from the ALLOWED INPUTS list above +# # - Double-check every signal name before using it! + +# # Now write the test scenario code to cover the missing blocks: +# # """ + +# # # === [新增] 注入多样性约束 === +# # if self.diversity_injector: +# # # 获取未覆盖功能点 +# # uncovered_functions = [] +# # if self.semantic_result and self.semantic_result.get('function_points'): +# # uncovered_functions = [ +# # fp for fp in self.semantic_result['function_points'] +# # if not fp.get('covered', False) +# # ] + +# # # 获取当前目标功能点 +# # target_function = "" +# # if self.energy_allocator and self.energy_allocator.current_target: +# # target_function = self.energy_allocator.current_target.function_point + +# # # 注入多样性约束 +# # prompt = self.diversity_injector.inject_diversity_constraints( +# # prompt=prompt, +# # target_function=target_function, +# # uncovered_functions=uncovered_functions +# # ) +# # # ================================= + +# # return prompt + +# # def _analyze_fsm_missing(self, missing_lines: List[str]) -> str: +# # """分析 FSM 相关的缺失代码,生成具体的 FSM 状态转换指导""" +# # analysis = [] + +# # # 检查是否涉及 FSM 状态转换 +# # has_state_case = any('case' in line.lower() and 'state' in line.lower() for line in missing_lines) +# # has_else_if = any('else if' in line.lower() for line in missing_lines) +# # has_if_condition = any(re.search(r'\bif\s*\(', line) for line in missing_lines) + +# # if has_state_case or has_else_if: +# # analysis.append("- Missing code involves FSM state transitions or conditional branches") + +# # if has_else_if or has_if_condition: +# # analysis.append("- Conditional branches have PRIORITY ORDER (top to bottom)") +# # analysis.append("- 'else if' branches require ALL previous conditions to be FALSE") +# # analysis.append("- Analyze the missing code's context: what conditions block this branch?") + +# # if has_state_case: +# # analysis.append("- To trigger a state transition: first reach the source state, then drive inputs") + +# # # === 新增:FSM 状态路径分析 === +# # # 尝试从缺失代码中提取 FSM 状态信息 +# # fsm_state_info = self._extract_fsm_state_from_missing(missing_lines) +# # if fsm_state_info: +# # analysis.append("") +# # analysis.append("[FSM STATE PATH ANALYSIS]") +# # analysis.extend(fsm_state_info) + +# # return "\n".join(analysis) if analysis else "" + +# # def _extract_fsm_state_from_missing(self, missing_lines: List[str]) -> List[str]: +# # """ +# # 从缺失代码中提取 FSM 状态信息,生成具体的状态转换指导 + +# # 分析策略: +# # 1. 从缺失代码的上下文识别 case 分支(FSM 状态) +# # 2. 分析该状态下的条件分支优先级 +# # 3. 识别需要满足的输入条件 +# # """ +# # info = [] + +# # # 从 annotated 文件中读取完整的 DUT 代码以分析 FSM 结构 +# # try: +# # with open(self.file_path, 'r', encoding='utf-8', errors='ignore') as f: +# # full_content = f.read() +# # except: +# # return info + +# # # 提取缺失代码所在的 FSM 状态 +# # target_state = None +# # missing_condition = None + +# # for line in missing_lines: +# # # 查找 case 分支标记(如 "WL:", "WR:", "FALLL:" 等) +# # # 格式可能是 "Line N: STATE:" 或 "STATE:" +# # state_match = re.search(r'\b([A-Z][A-Z0-9_]*)\s*:', line) +# # if state_match: +# # potential_state = state_match.group(1) +# # # 排除常见的非状态关键字 +# # if potential_state not in ['IF', 'ELSE', 'CASE', 'BEGIN', 'END', 'DEFAULT']: +# # target_state = potential_state +# # break + +# # # 如果没找到,尝试从整个文件中分析 +# # if not target_state: +# # # 查找缺失行附近的 case 分支 +# # lines = full_content.split('\n') +# # for i, line in enumerate(lines): +# # # 查找覆盖率标记为 0 的行 +# # if re.match(r'^%000000', line.strip()): +# # # 向上查找最近的 case 分支(状态) +# # for j in range(i-1, max(0, i-20), -1): +# # state_match = re.search(r'^\s*([A-Z][A-Z0-9_]*)\s*:', lines[j]) +# # if state_match: +# # target_state = state_match.group(1) +# # break +# # if target_state: +# # break + +# # # 分析缺失的条件分支 +# # for line in missing_lines: +# # # 提取 else if 条件 +# # else_if_match = re.search(r'else\s+if\s*\(([^)]+)\)', line) +# # if else_if_match: +# # missing_condition = else_if_match.group(1) +# # break +# # # 提取 if 条件 +# # if_match = re.search(r'\bif\s*\(([^)]+)\)', line) +# # if if_match: +# # missing_condition = if_match.group(1) +# # break + +# # # 生成具体的指导信息 +# # if target_state: +# # info.append(f"- Target FSM state identified: {target_state}") + +# # # 查找复位后的初始状态 +# # reset_state = self._find_reset_state(full_content) +# # if reset_state: +# # info.append(f"- After reset, FSM starts in state: {reset_state}") + +# # if reset_state != target_state: +# # info.append(f"- CRITICAL: You must FIRST transition from {reset_state} to {target_state}!") +# # info.append(f"- Do NOT assume FSM will automatically reach {target_state}!") + +# # # 尝试找到状态转换路径 +# # transition_hint = self._find_state_transition_hint(full_content, reset_state, target_state) +# # if transition_hint: +# # info.append(f"- To reach {target_state}: {transition_hint}") + +# # if missing_condition: +# # info.append(f"- Missing condition: \"{missing_condition}\"") +# # # 分析条件优先级 +# # priority_info = self._analyze_condition_priority(full_content, target_state, missing_condition) +# # if priority_info: +# # info.extend(priority_info) + +# # return info + +# # def _find_reset_state(self, content: str) -> Optional[str]: +# # """从 DUT 代码中找到复位后的初始状态""" +# # # 查找复位逻辑中的状态赋值 +# # # 常见模式: if (reset) state <= IDLE; 或 state <= 0; +# # patterns = [ +# # r'if\s*\([^)]*reset[^)]*\)\s*state\s*<=\s*([A-Z][A-Z0-9_]*);', +# # r'if\s*\([^)]*reset[^)]*\)\s*state\s*<=\s*(\d+);', +# # r'if\s*\([^)]*rst[^)]*\)\s*state\s*<=\s*([A-Z][A-Z0-9_]*);', +# # ] + +# # for pattern in patterns: +# # match = re.search(pattern, content, re.IGNORECASE) +# # if match: +# # state = match.group(1) +# # # 如果是数字,尝试从参数中找对应的状态名 +# # if state.isdigit(): +# # # 查找参数定义 +# # param_match = re.search(r'parameter\s+([^;]+);', content) +# # if param_match: +# # params = param_match.group(1) +# # # 解析参数列表 +# # for param in params.split(','): +# # param = param.strip() +# # if '=' in param: +# # name, value = param.split('=') +# # if value.strip() == state: +# # return name.strip() +# # return state + +# # return None + +# # def _find_state_transition_hint(self, content: str, from_state: str, to_state: str) -> Optional[str]: +# # """找到从一个状态到另一个状态的转换条件""" +# # # 在 case 语句中查找 from_state 分支 +# # # 提取该分支下到 to_state 的转换条件 + +# # # 简单策略:查找 "next = TO_STATE" 或 "next <= TO_STATE" +# # pattern = rf'{from_state}\s*:.*?next\s*=?\s*{to_state}' +# # match = re.search(pattern, content, re.DOTALL) + +# # if match: +# # # 提取条件 +# # branch_code = match.group(0) +# # # 查找 if 条件 +# # if_match = re.search(r'if\s*\(([^)]+)\)\s*next\s*=?\s*' + to_state, branch_code) +# # if if_match: +# # return f"set condition: {if_match.group(1)}" + +# # # 查找 else if 条件 +# # elif_match = re.search(r'else\s+if\s*\(([^)]+)\)\s*next\s*=?\s*' + to_state, branch_code) +# # if elif_match: +# # return f"set condition: {elif_match.group(1)} (ensure earlier conditions are FALSE)" + +# # # 尝试反向查找:什么条件下会转换到目标状态 +# # trans_pattern = rf'(?:if|else\s+if)\s*\(([^)]+)\)\s*(?:next\s*=?\s*{to_state}|{to_state}\s*;)' +# # trans_match = re.search(trans_pattern, content) +# # if trans_match: +# # return f"set condition: {trans_match.group(1)}" + +# # return None + +# # def _analyze_condition_priority(self, content: str, state: str, missing_condition: str) -> List[str]: +# # """分析条件分支的优先级,找出需要排除的条件""" +# # info = [] + +# # if not state: +# # return info + +# # # 查找该状态下的所有条件分支 +# # # 提取 state: 后面的代码块 +# # state_block_pattern = rf'{state}\s*:(.*?)(?=[A-Z][A-Z0-9_]*\s*:|endcase|default:)' +# # match = re.search(state_block_pattern, content, re.DOTALL) + +# # if not match: +# # return info + +# # state_block = match.group(1) + +# # # 提取所有条件分支 +# # conditions = [] +# # for cond_match in re.finditer(r'(?:if|else\s+if)\s*\(([^)]+)\)', state_block): +# # conditions.append(cond_match.group(1).strip()) + +# # # 找到缺失条件在列表中的位置 +# # missing_idx = -1 +# # for i, cond in enumerate(conditions): +# # # 简化比较(去除空格) +# # if cond.replace(' ', '') in missing_condition.replace(' ', '') or \ +# # missing_condition.replace(' ', '') in cond.replace(' ', ''): +# # missing_idx = i +# # break + +# # if missing_idx > 0: +# # info.append(f"- This branch is condition #{missing_idx + 1} (lower priority)") +# # info.append(f"- You must make ALL earlier conditions FALSE:") +# # for i in range(missing_idx): +# # cond = conditions[i] +# # # 分析如何使条件为 FALSE +# # false_hint = self._get_false_hint(cond) +# # info.append(f" * \"{cond}\" must be FALSE → {false_hint}") + +# # return info + +# # def _get_false_hint(self, condition: str) -> str: +# # """分析如何使条件为 FALSE""" +# # condition = condition.strip() + +# # # 处理 !signal 形式 +# # if condition.startswith('!'): +# # signal = condition[1:].strip() +# # return f"set {signal} = 1" + +# # # 处理 signal 形式(布尔值) +# # if re.match(r'^[a-zA-Z_][a-zA-Z0-9_]*$', condition): +# # return f"set {condition} = 0" + +# # # 处理比较运算符 +# # if '==' in condition: +# # parts = condition.split('==') +# # if len(parts) == 2: +# # signal = parts[0].strip() +# # value = parts[1].strip() +# # if value.isdigit(): +# # return f"set {signal} != {value}" + +# # # 处理 >= 形式 +# # if '>=' in condition: +# # parts = condition.split('>=') +# # if len(parts) == 2: +# # signal = parts[0].strip() +# # value = parts[1].strip() +# # if value.isdigit(): +# # return f"set {signal} < {value}" + +# # # 处理 > 形式 +# # if '>' in condition and '>=' not in condition: +# # parts = condition.split('>') +# # if len(parts) == 2: +# # signal = parts[0].strip() +# # value = parts[1].strip() +# # return f"set {signal} <= {value}" + +# # return "analyze the condition logic" + +# # def _generate_semantic_context(self) -> str: +# # """ +# # [新增] 从语义分析结果生成 Prompt 上下文 + +# # 整合语义分析层 (Layer 0) 的输出,为 LLM 提供更精准的指导: +# # - FSM 状态转换图 +# # - 功能点重要性排序 +# # - 测试场景建议 + +# # Returns: +# # 语义上下文字符串,用于增强 Prompt +# # """ +# # if not self.semantic_result: +# # return "" + +# # context_parts = [] + +# # # 1. 模块基础信息 +# # module_name = self.semantic_result.get('module_name', '') +# # inputs = self.semantic_result.get('inputs', []) +# # outputs = self.semantic_result.get('outputs', []) + +# # if module_name: +# # context_parts.append(f"Module Name: {module_name}") +# # if inputs: +# # context_parts.append(f"Module Inputs: {', '.join(inputs)}") +# # if outputs: +# # context_parts.append(f"Module Outputs: {', '.join(outputs)}") + +# # # 2. FSM 信息(最关键) +# # fsm_info = self.semantic_result.get('fsm_info') +# # if fsm_info: +# # context_parts.append("") +# # context_parts.append("=== FSM STATE MACHINE DETAILS ===") +# # context_parts.append(f"State Variable: {fsm_info.get('state_variable', 'unknown')}") + +# # states = fsm_info.get('states', []) +# # if states: +# # context_parts.append(f"All States ({len(states)}): {', '.join(states)}") + +# # # 状态转换表 +# # transitions = fsm_info.get('transitions', {}) +# # if transitions: +# # context_parts.append("") +# # context_parts.append("=== STATE TRANSITION TABLE ===") +# # context_parts.append("Format: CURRENT_STATE --[CONDITION]--> NEXT_STATE") +# # context_parts.append("") + +# # for state, trans_list in transitions.items(): +# # for trans in trans_list: +# # condition = trans.get('condition', 'default') +# # next_state = trans.get('next_state', 'unknown') +# # if condition == 'default': +# # context_parts.append(f" {state} --[default]--> {next_state}") +# # else: +# # context_parts.append(f" {state} --[if ({condition})]--> {next_state}") + +# # # 添加状态转换路径分析 +# # context_parts.append("") +# # context_parts.append("=== STATE TRANSITION PATH HINTS ===") +# # reset_state = self._find_reset_state_from_fsm(fsm_info) +# # if reset_state: +# # context_parts.append(f"Initial State (after reset): {reset_state}") +# # context_parts.append("") +# # context_parts.append("IMPORTANT: To reach a target state, trace the path from reset:") +# # context_parts.append(" 1. Reset the DUT to initialize to the starting state") +# # context_parts.append(" 2. Apply inputs to trigger state transitions") +# # context_parts.append(" 3. Wait for the FSM to naturally reach the target state") +# # context_parts.append(" 4. THEN apply inputs to trigger the missing branch") + +# # # 3. 功能点优先级 +# # function_points = self.semantic_result.get('function_points', []) +# # if function_points: +# # context_parts.append("") +# # context_parts.append("=== FUNCTION POINTS (Ranked by Importance) ===") + +# # for i, fp in enumerate(function_points[:10]): # Top 10 +# # name = fp.get('name', 'unknown') +# # fp_type = fp.get('type', 'unknown') +# # importance = fp.get('importance', 0) +# # covered = fp.get('covered', False) +# # status = "✓ COVERED" if covered else "✗ NOT COVERED" +# # context_parts.append(f" {i+1}. [{status}] {name} ({fp_type}): importance={importance:.2f}") + +# # # 4. 测试场景建议 +# # test_scenarios = self.semantic_result.get('test_scenarios', []) +# # if test_scenarios: +# # context_parts.append("") +# # context_parts.append("=== RECOMMENDED TEST SCENARIOS ===") + +# # for i, ts in enumerate(test_scenarios[:5]): # Top 5 +# # name = ts.get('name', 'unknown') +# # description = ts.get('description', '') +# # priority = ts.get('priority', 0) +# # context_parts.append(f" {i+1}. {name}: {description} (priority={priority:.2f})") + +# # if context_parts: +# # return "\n".join(context_parts) +# # return "" + +# # def _find_reset_state_from_fsm(self, fsm_info: dict) -> Optional[str]: +# # """从 FSM 信息中推断复位后的初始状态""" +# # # 方法1:检查是否有明确的复位状态 +# # transitions = fsm_info.get('transitions', {}) + +# # # 复位后通常进入第一个定义的状态或特定名称的状态 +# # states = fsm_info.get('states', []) + +# # # 常见的初始状态命名 +# # initial_state_names = ['IDLE', 'INIT', 'RESET', 'START', 'BEGIN'] + +# # for name in initial_state_names: +# # if name in states: +# # return name + +# # # 如果没有找到,返回第一个状态 +# # if states: +# # return states[0] + +# # return None + + +# # # ============================================================================ +# # # TBInjector - 场景注入器 +# # # ============================================================================ +# # class TBInjector: +# # """ +# # 场景注入器 - 将LLM生成的测试代码注入到现有测试平台 + +# # 集成三层防护策略: +# # 1. Layer 1: Prompt约束(由CoverageParser处理) +# # 2. Layer 2: 智能代码转换 +# # 3. Layer 3: 质量评估和重试建议 +# # """ + +# # def __init__(self, tb_code): +# # """ +# # 初始化注入器 + +# # Args: +# # tb_code: 原始测试平台代码字符串 +# # """ +# # self.content = tb_code +# # self.validator = BlackBoxValidator() +# # self.validator._extract_signals_from_tb(tb_code) +# # self.last_validation_result = None + +# # def inject(self, new_code, iter_idx): +# # """ +# # 注入新的测试场景到测试平台 + +# # Args: +# # new_code: LLM生成的测试代码 +# # iter_idx: 迭代序号 + +# # Returns: +# # 修改后的测试平台代码 +# # """ +# # # Step 1: 预处理代码(包含三层防护) +# # scenario_code, result = self._preprocess_code(new_code, iter_idx) + +# # self.last_validation_result = result + +# # # 记录日志 +# # if result['violations']['critical']: +# # logger.warning(f"[CGA-{iter_idx}] Critical violations detected:") +# # for v in result['violations']['critical']: +# # logger.warning(f" - {v}") + +# # if result['violations']['warning']: +# # logger.info(f"[CGA-{iter_idx}] Warnings:") +# # for v in result['violations']['warning']: +# # logger.info(f" - {v}") + +# # if result['transformations']: +# # logger.info(f"[CGA-{iter_idx}] Code transformations applied:") +# # for t in result['transformations']: +# # logger.info(f" - {t['type']}: {t.get('original', 'N/A')[:50]}...") + +# # # Step 2: 构建场景块 +# # scenario_block = self._build_scenario_block(scenario_code, iter_idx) + +# # # Step 3: 注入到TB中 +# # modified_tb = self._inject_scenario(scenario_block) + +# # return modified_tb + +# # def should_retry(self): +# # """是否应该重试""" +# # if self.last_validation_result is None: +# # return False +# # return self.last_validation_result.get('should_retry', False) + +# # def get_quality_score(self): +# # """获取代码质量分数""" +# # if self.last_validation_result is None: +# # return 0 +# # return self.last_validation_result.get('quality_score', 0) + +# # def _preprocess_code(self, code, iter_idx): +# # """预处理LLM生成的代码""" +# # # 移除markdown标记 +# # code = re.sub(r'```(?:verilog|systemverilog|sv)?\n?', '', code) +# # code = re.sub(r'```', '', code) + +# # # 移除task包装 +# # code = re.sub(r'task\s+\w+\s*(?:\([^)]*\))?\s*;', '', code) +# # code = re.sub(r'endtask', '', code) + +# # # 移除$finish和$stop +# # code = re.sub(r'\$finish\s*;', '', code) +# # code = re.sub(r'\$stop\s*;', '', code) + +# # # 移除多余空行 +# # code = re.sub(r'\n\s*\n\s*\n', '\n\n', code) + +# # initial_pattern = re.compile(r'\binitial\s+begin\b.*?\bend\b', re.DOTALL | re.IGNORECASE) +# # # 检查并移除 initial begin ... end 块 +# # initial_match = initial_pattern.search(code) +# # if initial_match: +# # logger.warning(f"[CGA-{iter_idx}] Detected 'initial begin...end' block in generated code - this should not be included") +# # logger.warning(f"[CGA-{iter_idx}] Removing 'initial begin...end' wrapper, keeping only the test content") +# # # 提取块内的内容 +# # block_content = initial_match.group(0) +# # # 移除 initial begin 和 end 包装 +# # # 保留块内的实际测试代码 +# # inner_content = re.sub(r'^\s*initial\s+begin\s*', '', block_content) +# # inner_content = re.sub(r'\bend\s*$', '', inner_content) +# # # 替换整个块为内部内容 +# # code = initial_pattern.sub(inner_content.strip(), code, count=1) + +# # code = re.sub(r'\n\s*\n\s*\n', '\n\n', code) +# # # 信号名自动修正(在验证之前) +# # code = self._auto_correct_signal_names(code) + +# # # 三层防护:黑盒约束验证和转换 +# # code, result = self.validator.validate_and_transform(code, self.content) + +# # code = re.sub(r'\n\s*\n\s*\n', '\n\n', code) + +# # return code.strip(), result + +# # def _auto_correct_signal_names(self, code: str) -> str: +# # """自动修正信号名错误""" +# # corrections = [] + +# # # 获取正确的复位信号名 +# # reset_signal = self.validator._find_reset_signal() + +# # # 如果正确的复位信号不是 'reset',则修正所有 'reset' 引用 +# # if reset_signal != "reset": +# # # 匹配独立的 'reset' 单词(不包括 'areset', 'rst_n' 等) +# # pattern = r'\breset\b(?!\w)' +# # matches = re.findall(pattern, code) +# # if matches: +# # code = re.sub(pattern, reset_signal, code) +# # corrections.append(f"reset -> {reset_signal} ({len(matches)} occurrences)") + +# # # 检查是否有使用 'rst' 但正确信号是 'areset' 的情况 +# # if reset_signal == "areset": +# # pattern = r'\brst\b(?!\w)' +# # matches = re.findall(pattern, code) +# # if matches: +# # code = re.sub(pattern, reset_signal, code) +# # corrections.append(f"rst -> {reset_signal} ({len(matches)} occurrences)") + +# # # 检查是否使用了不存在的信号 +# # for signal in re.findall(r'\b(\w+)\s*=', code): +# # signal = signal.strip() +# # # 跳过已知的合法信号 +# # if signal in self.validator.dut_inputs: +# # continue +# # # 检查是否是复位信号的别名 +# # if signal.lower() in ['reset', 'rst', 'rst_n', 'rst_b'] and reset_signal != signal: +# # code = re.sub(rf'\b{signal}\b', reset_signal, code) +# # corrections.append(f"{signal} -> {reset_signal}") + +# # if corrections: +# # logger.info(f"[Signal Correction] Applied corrections: {'; '.join(corrections)}") + +# # return code + +# # def _build_scenario_block(self, scenario_code, iter_idx): +# # """构建完整的场景代码块""" +# # # 格式化缩进 +# # lines = scenario_code.split('\n') +# # formatted_lines = [] +# # for line in lines: +# # stripped = line.strip() +# # if stripped: +# # formatted_lines.append(f" {stripped}") +# # formatted_code = '\n'.join(formatted_lines) + +# # # 检测输出信号用于日志 +# # output_signals = self._detect_output_signals() +# # output_log = self._generate_output_log(output_signals, iter_idx) + +# # # 构建完整块 +# # block = f''' +# # // ========== CGA Iteration {iter_idx} ========== +# # scenario = 100 + {iter_idx}; +# # // Reset signals to safe state +# # {self._generate_signal_reset()} +# # #5; +# # // CGA generated test sequence: +# # {formatted_code} +# # // Log results +# # {output_log} +# # // ============================================== +# # ''' +# # return block + +# # def _detect_output_signals(self): +# # """检测DUT的输出信号""" +# # outputs = [] +# # wire_pattern = re.compile(r'wire\s+(?:\[[\d:]+\]\s*)?(\w+)\s*;') +# # for match in wire_pattern.finditer(self.content): +# # signal = match.group(1) +# # if signal.lower() not in ['clk', 'clock', 'rst', 'reset', 'areset']: +# # outputs.append(signal) +# # return outputs + +# # def _generate_signal_reset(self): +# # """生成信号重置代码""" +# # inputs = [] +# # reg_pattern = re.compile(r'reg\s+(?:\[[\d:]+\]\s*)?(\w+)\s*;') +# # for match in reg_pattern.finditer(self.content): +# # signal = match.group(1) +# # if signal.lower() not in ['clk', 'clock', 'file', 'scenario']: +# # inputs.append(signal) + +# # if inputs: +# # return " " + "; ".join([f"{sig} = 0" for sig in inputs]) + ";" +# # return " // No input signals to reset" + +# # def _generate_output_log(self, signals, iter_idx): +# # """生成输出日志代码""" +# # if not signals: +# # return f' $display("[CGA-{iter_idx}] Scenario executed");' + +# # sig_names = ", ".join(signals) +# # format_str = ", ".join(["%b"] * len(signals)) + +# # return f' $fdisplay(file, "[CGA-{iter_idx}] {sig_names} = {format_str}", {sig_names});' + +# # def _inject_scenario(self, scenario_block): +# # """将场景块注入到测试平台""" +# # modified_tb = self.content + +# # # 策略:如果有 $fclose,在其之前插入 +# # if "$fclose" in modified_tb: +# # modified_tb = re.sub( +# # r'(\s*)(\$fclose\s*\([^)]+\)\s*;)', +# # scenario_block + r'\1\2', +# # modified_tb, +# # count=1 +# # ) +# # elif "$finish" in modified_tb: +# # # 否则在 $finish 之前插入 +# # modified_tb = modified_tb.replace( +# # "$finish;", +# # scenario_block + "\n $finish;" +# # ) +# # else: +# # # 兜底:在最后一个 end 之前插入 +# # last_end = modified_tb.rfind("end") +# # if last_end != -1: +# # modified_tb = modified_tb[:last_end] + scenario_block + modified_tb[last_end:] + +# # return modified_tb + + +# """ +# Description : Utils for CGA (CoverageParser & TBInjector) +# - Features: Sticky Mode, Smart Noise Filtering (No assign/decls) +# - Enhanced: Three-layer protection for black-box constraints +# * Layer 1: Enhanced Prompt constraints (prevention) +# * Layer 2: Smart code transformation (conversion) +# * Layer 3: Quality assessment & retry (fallback) +# - Integrated: Diversity Constraint Injector (Layer 1) +# Author : CorrectBench Integration +# """ +# import re +# import os +# import logging +# from typing import Tuple, List, Optional, Dict, TYPE_CHECKING + +# # [新增] 导入多样性约束注入器 +# if TYPE_CHECKING: +# from autoline.diversity_injector import DiversityInjector + +# # 配置日志 +# logger = logging.getLogger(__name__) + +# # ============================================================================ +# # 三层防护策略说明 +# # ============================================================================ +# # Layer 1 - Prompt约束: 动态提取允许信号列表,明确约束,正反面示例 +# # Layer 2 - 智能转换: 检测违规意图,尝试转换为合法形式 +# # Layer 3 - 质量评估: 违规比例过高时返回质量分数,触发重新生成 +# # ============================================================================ + + +# # ============================================================================ +# # 黑盒约束检查器 - 三层防护策略实现 +# # ============================================================================ +# class BlackBoxValidator: +# """ +# 黑盒约束验证器 - 三层防护策略 + +# Layer 1: 增强Prompt约束(预防) +# - 动态提取允许信号列表 +# - 生成明确的约束提示 + +# Layer 2: 智能代码转换(转换) +# - 检测违规意图 +# - 尝试转换为合法的等价形式 +# - 转换失败时才注释 + +# Layer 3: 质量评估(重试) +# - 计算代码质量分数 +# - 违规比例过高时建议重试 +# """ + +# # 常见的内部信号命名模式(按严重程度分类) +# INTERNAL_SIGNAL_PATTERNS = { +# # 高风险:FSM状态相关(绝对不能修改) +# 'critical': [ +# (r'\bstate\b', 'FSM状态寄存器'), +# (r'\bnext_state\b', 'FSM下一状态'), +# (r'\bcurrent_state\b', 'FSM当前状态'), +# (r'\bnext\b(?!\s*[,@])', '下一状态简写'), +# ], +# # 中风险:计数器和内部寄存器 +# 'warning': [ +# (r'\bcounter\b', '内部计数器'), +# (r'\bcount\b', '计数寄存器'), +# (r'\bcnt\b', '计数简写'), +# (r'\bfall_counter\b', '下落计数器'), +# (r'\breg_\w+', '内部寄存器'), +# ], +# # 低风险:可疑信号(需要确认) +# 'info': [ +# (r'\binternal_\w+', '内部信号'), +# (r'\btemp_\w+', '临时信号'), +# (r'\bprev_\w+', '前一状态'), +# ] +# } + +# # 非法语句模式 +# FORBIDDEN_STATEMENTS = [ +# (r'\bforce\s+(\w+)', 'force语句', 'critical'), +# (r'\bassign\s+(\w+)\s*=', '连续赋值', 'critical'), +# (r'\bdeassign\s+', 'deassign语句', 'critical'), +# (r'\brelease\s+', 'release语句', 'critical'), +# ] + +# # 层次化访问模式(如 DUT.state) +# HIERARCHICAL_ACCESS = r'(\w+)\s*\.\s*(\w+)' + +# def __init__(self, dut_inputs: List[str] = None, dut_outputs: List[str] = None): +# """ +# Args: +# dut_inputs: DUT模块的输入端口列表 +# dut_outputs: DUT模块的输出端口列表 +# """ +# self.dut_inputs = dut_inputs or [] +# self.dut_outputs = dut_outputs or [] +# self.violations = {'critical': [], 'warning': [], 'info': []} +# self.transformations = [] + +# def validate_and_transform(self, code: str, tb_code: str = None) -> Tuple[str, Dict]: +# """验证并转换代码 - 主入口""" +# self.violations = {'critical': [], 'warning': [], 'info': []} +# self.transformations = [] + +# if tb_code: +# self._extract_signals_from_tb(tb_code) + +# original_lines = code.strip().split('\n') +# total_lines = len([l for l in original_lines if l.strip() and not l.strip().startswith('//')]) + +# # Step 1: 移除非法语句 +# code = self._transform_forbidden_statements(code) + +# # Step 2: 转换层次化访问 +# code = self._transform_hierarchical_access(code) + +# # Step 3: 智能转换内部信号访问 +# code = self._smart_transform_internal_signals(code) + +# # Step 4: 最后清理 +# code = self._final_cleanup(code) + +# # 计算质量分数 +# quality_score = self._calculate_quality_score(total_lines) + +# # 决定是否需要重试 +# should_retry = quality_score < 50 or len(self.violations['critical']) > 3 + +# result = { +# 'quality_score': quality_score, +# 'is_valid': len(self.violations['critical']) == 0, +# 'violations': self.violations, +# 'transformations': self.transformations, +# 'should_retry': should_retry, +# 'allowed_signals': self._get_allowed_signals_info() +# } + +# return code.strip(), result + +# def _extract_signals_from_tb(self, tb_code: str): +# """从测试平台代码中提取DUT输入输出信号""" +# dut_match = re.search(r'(\w+)\s+(?:DUT|dut|uut|UUT)\s*\(', tb_code, re.IGNORECASE) +# if dut_match: +# start = dut_match.start() +# bracket_count = 0 +# end = start +# for i, char in enumerate(tb_code[start:]): +# if char == '(': +# bracket_count += 1 +# elif char == ')': +# bracket_count -= 1 +# if bracket_count == 0: +# end = start + i + 1 +# break + +# dut_instance = tb_code[start:end] +# port_pattern = r'\.(\w+)\s*\(\s*(\w+)\s*\)' + +# for match in re.finditer(port_pattern, dut_instance): +# signal_name = match.group(2) + +# is_input = re.search(rf'\breg\s+(?:\[[\d:]+\]\s*)?{re.escape(signal_name)}\s*[;,\n]', tb_code) +# is_output = re.search(rf'\bwire\s+(?:\[[\d:]+\]\s*)?{re.escape(signal_name)}\s*[;,\n]', tb_code) + +# if is_input and signal_name not in self.dut_inputs: +# self.dut_inputs.append(signal_name) +# if is_output and signal_name not in self.dut_outputs: +# self.dut_outputs.append(signal_name) + +# # 备用方案:通过reg/wire声明推断 +# if not self.dut_inputs and not self.dut_outputs: +# for match in re.finditer(r'\breg\s+(?:\[[\d:]+\]\s*)?(\w+)\s*[;,\n]', tb_code): +# signal = match.group(1) +# if signal.lower() not in ['file', 'scenario', 'i', 'j', 'k', 'cnt']: +# if signal not in self.dut_inputs: +# self.dut_inputs.append(signal) + +# for match in re.finditer(r'\bwire\s+(?:\[[\d:]+\]\s*)?(\w+)\s*[;,\n]', tb_code): +# signal = match.group(1) +# if signal not in self.dut_outputs: +# self.dut_outputs.append(signal) + +# def _transform_forbidden_statements(self, code: str) -> str: +# """转换非法语句""" +# for pattern, desc, severity in self.FORBIDDEN_STATEMENTS: +# matches = list(re.finditer(pattern, code, re.IGNORECASE)) +# for match in reversed(matches): +# signal = match.group(1) if match.groups() else 'unknown' +# self.violations[severity].append(f"{desc}: {signal}") + +# line_start = code.rfind('\n', 0, match.start()) + 1 +# line_end = code.find('\n', match.end()) +# if line_end == -1: +# line_end = len(code) +# original_line = code[line_start:line_end] + +# # 尝试转换 force -> 直接赋值(仅对输入信号) +# if 'force' in match.group(0).lower() and signal in self.dut_inputs: +# new_line = re.sub(r'\bforce\s+', '', original_line, flags=re.IGNORECASE) +# code = code[:line_start] + new_line + code[line_end:] +# self.transformations.append({ +# 'type': 'force_to_assign', +# 'original': original_line.strip(), +# 'transformed': new_line.strip() +# }) +# continue + +# code = code[:line_start] + '// [BLOCKED] ' + original_line.lstrip() + code[line_end:] +# self.transformations.append({ +# 'type': 'blocked', +# 'original': original_line.strip(), +# 'reason': desc +# }) + +# return code + +# def _transform_hierarchical_access(self, code: str) -> str: +# """转换层次化访问(如 DUT.state)""" +# for match in re.finditer(self.HIERARCHICAL_ACCESS, code): +# prefix = match.group(1) +# signal = match.group(2) + +# if prefix.upper() in ['DUT', 'UUT', 'TOP', 'TB']: +# if signal not in self.dut_outputs: +# self.violations['critical'].append(f"层次化访问内部信号: {prefix}.{signal}") + +# line_start = code.rfind('\n', 0, match.start()) + 1 +# line_end = code.find('\n', match.end()) +# if line_end == -1: +# line_end = len(code) +# original_line = code[line_start:line_end] +# code = code[:line_start] + '// [HIERARCHY] ' + original_line.lstrip() + code[line_end:] + +# return code + +# def _smart_transform_internal_signals(self, code: str) -> str: +# """智能转换内部信号访问""" +# lines = code.split('\n') +# transformed_lines = [] + +# for line in lines: +# stripped = line.strip() + +# if stripped.startswith('//') or not stripped: +# transformed_lines.append(line) +# continue + +# if (stripped.startswith('#') or stripped.startswith('$') or +# stripped.startswith('repeat(') or stripped.startswith('@(')): +# transformed_lines.append(line) +# continue + +# detected_signals = self._detect_internal_signals_in_line(stripped) +# has_critical = detected_signals.get('critical', []) +# has_warning = detected_signals.get('warning', []) + +# if not has_critical and not has_warning: +# transformed_lines.append(line) +# continue + +# context = self._analyze_signal_context(stripped, detected_signals) + +# if context['type'] == 'assignment': +# transformed_lines.append(f"// [INTERNAL_ASSIGN] Cannot modify internal signal") +# transformed_lines.append(f"// Original: {stripped}") +# self.violations['critical'].append(f"尝试修改内部信号: {context['signals']}") +# elif context['type'] == 'condition': +# transformed = self._transform_condition(stripped, context) +# transformed_lines.append(transformed) +# self.transformations.append({ +# 'type': 'condition_transform', +# 'original': stripped, +# 'transformed': transformed +# }) +# elif context['type'] == 'wait_for_state': +# transformed = self._transform_state_wait(stripped, context) +# transformed_lines.append(transformed) +# self.transformations.append({ +# 'type': 'wait_transform', +# 'original': stripped, +# 'transformed': transformed +# }) +# else: +# if has_critical: +# transformed_lines.append(f"// [WARNING] Contains internal signal reference: {has_critical}") +# transformed_lines.append(f"// Original: {stripped}") +# for sig in has_critical: +# self.violations['warning'].append(f"可疑的内部信号访问: {sig}") +# else: +# transformed_lines.append(line) + +# return '\n'.join(transformed_lines) + +# def _detect_internal_signals_in_line(self, line: str) -> Dict[str, List[str]]: +# """检测行中的内部信号""" +# detected = {'critical': [], 'warning': [], 'info': []} + +# LEGAL_KEYWORDS = { +# 'repeat', 'posedge', 'negedge', 'begin', 'end', 'if', 'else', +# 'while', 'for', 'case', 'default', 'always', 'initial', +# 'assign', 'wire', 'reg', 'input', 'output', 'inout', +# 'parameter', 'localparam', 'integer', 'real', 'time', +# 'clk', 'clock', 'reset', 'rst', 'areset', 'rst_n', +# 'enable', 'ena', 'valid', 'ready', 'data', 'addr', 'address', +# 'true', 'false', 'idle', 'wait' +# } + +# SYSTEM_FUNCTIONS = {'$display', '$write', '$monitor', '$fopen', '$fclose', +# '$fdisplay', '$fwrite', '$readmemh', '$readmemb', +# '$finish', '$stop', '$random', '$time', '$stime'} + +# for severity, patterns in self.INTERNAL_SIGNAL_PATTERNS.items(): +# for pattern, name in patterns: +# matches = re.findall(pattern, line, re.IGNORECASE) +# if matches: +# for match in matches: +# if isinstance(match, tuple): +# match = match[0] if match[0] else match[1] + +# match_lower = match.lower() if match else '' + +# if match_lower in LEGAL_KEYWORDS: +# continue +# if match in SYSTEM_FUNCTIONS: +# continue +# if match in self.dut_inputs or match in self.dut_outputs: +# continue +# if match.startswith('$'): +# continue + +# if match and match not in detected[severity]: +# detected[severity].append(match) + +# return detected + +# def _analyze_signal_context(self, line: str, signals: Dict) -> Dict: +# """分析信号使用上下文""" +# assign_match = re.search(r'(\w+)\s*(?:=|<=)\s*', line) +# if assign_match: +# target = assign_match.group(1) +# if target in signals.get('critical', []) or target in signals.get('warning', []): +# return {'type': 'assignment', 'signals': [target], 'line': line} + +# if re.search(r'wait\s*\([^)]*state', line, re.IGNORECASE): +# return {'type': 'wait_for_state', 'signals': signals.get('critical', []), 'line': line} + +# if re.search(r'if\s*\(|while\s*\(|@\s*\(', line): +# return {'type': 'condition', 'signals': signals.get('critical', []) + signals.get('warning', []), 'line': line} + +# return {'type': 'other', 'signals': signals.get('critical', []) + signals.get('warning', []), 'line': line} + +# def _transform_condition(self, line: str, context: Dict) -> str: +# """转换条件判断语句""" +# original = line + +# if 'state' in str(context['signals']): +# indent = len(line) - len(line.lstrip()) +# spaces = ' ' * indent + +# transformed = f"// [TRANSFORMED] Original: {original.strip()}\n" +# transformed += f"{spaces}// Cannot directly check internal state\n" +# transformed += f"{spaces}// Alternative: Wait for expected clock cycles\n" +# transformed += f"{spaces}repeat(5) @(posedge clk); // Adjust cycles as needed" + +# self.violations['warning'].append(f"条件判断转换: {original.strip()}") +# return transformed + +# return f"// [TRANSFORMED] {original}" + +# def _transform_state_wait(self, line: str, context: Dict) -> str: +# """转换状态等待语句""" +# indent = len(line) - len(line.lstrip()) +# spaces = ' ' * indent + +# transformed = f"// [TRANSFORMED] Original: {line.strip()}\n" +# transformed += f"{spaces}// Cannot wait for internal state directly\n" +# transformed += f"{spaces}// Alternative: Drive inputs and wait for expected cycles\n" +# transformed += f"{spaces}repeat(10) @(posedge clk); // Adjust based on FSM design" + +# self.violations['info'].append(f"状态等待转换: {line.strip()}") +# return transformed + +# def _final_cleanup(self, code: str) -> str: +# """最终清理""" +# lines = code.split('\n') +# cleaned = [] + +# for line in lines: +# stripped = line.strip() + +# if stripped in ['begin', 'end'] and cleaned: +# last = cleaned[-1].strip() +# if last.startswith('// [TRANSFORMED]') or last.startswith('// [INTERNAL'): +# continue + +# cleaned.append(line) + +# result = '\n'.join(cleaned) +# result = re.sub(r'\n\s*\n\s*\n', '\n\n', result) + +# return result + +# def _calculate_quality_score(self, total_lines: int) -> int: +# """计算代码质量分数""" +# if total_lines == 0: +# return 0 + +# score = 100 +# score -= len(self.violations['critical']) * 20 +# score -= len(self.violations['warning']) * 5 +# score += len([t for t in self.transformations if 'blocked' not in t.get('type', '')]) * 5 + +# return max(0, min(100, score)) + +# def _get_allowed_signals_info(self) -> Dict: +# """获取允许的信号信息""" +# return { +# 'inputs': self.dut_inputs, +# 'outputs': self.dut_outputs, +# 'all_allowed': self.dut_inputs + self.dut_outputs +# } + +# def generate_constraint_prompt(self) -> str: +# """生成动态约束提示 - 使用实际信号名""" +# prompt = "\n[SIGNAL CONSTRAINTS - DERIVED FROM YOUR DUT]\n" + +# # 提取复位信号名(优先使用实际的) +# reset_signal = self._find_reset_signal() + +# if self.dut_inputs: +# prompt += "ALLOWED INPUTS (you CAN drive these):\n" +# for sig in self.dut_inputs: +# prompt += f" - {sig}\n" +# else: +# prompt += "ALLOWED INPUTS: Check the testbench for actual signal names\n" + +# if self.dut_outputs: +# prompt += "\nOUTPUTS (you can READ but NOT write):\n" +# for sig in self.dut_outputs: +# prompt += f" - {sig}\n" + +# prompt += f""" +# FORBIDDEN ACTIONS: +# 1. NEVER assign values to internal signals (state, counter, etc.) +# 2. NEVER use 'force' or 'assign' statements +# 3. NEVER access signals like DUT.state (hierarchical access) + +# CORRECT APPROACH: +# - To reach a specific FSM state: drive inputs and WAIT for natural transition +# - Example: Instead of "state = IDLE", use "{reset_signal} = 1; repeat(2) @(posedge clk); {reset_signal} = 0;" +# """ +# return prompt + +# def _find_reset_signal(self) -> str: +# """查找复位信号名""" +# # 按优先级查找常见的复位信号名 +# reset_candidates = ['areset', 'rst_n', 'rst', 'reset', 'rst_b'] +# for sig in reset_candidates: +# if sig in self.dut_inputs: +# return sig +# # 如果没找到,检查输入列表中是否有类似名称 +# for sig in self.dut_inputs: +# sig_lower = sig.lower() +# if 'reset' in sig_lower or 'rst' in sig_lower: +# return sig +# # 默认返回第一个输入信号(排除 clk) +# for sig in self.dut_inputs: +# if 'clk' not in sig.lower(): +# return sig +# return "reset" # 兜底 + +# # ========================================================================= +# # [新增] Verilog 语法预检查 - 检测常见逻辑错误 +# # ========================================================================= +# def check_syntax_issues(self, code: str, signal_widths: Dict[str, int] = None, declared_signals: set = None) -> Dict: +# """ +# 检测 Verilog 代码中的常见语法/逻辑问题 + +# Args: +# code: 待检查的代码 +# signal_widths: 信号位宽映射 (可选,如 {'in': 1, 'data': 8}) +# declared_signals: 已声明的信号集合 (可选,用于检测未声明信号) + +# Returns: +# { +# 'width_mismatch': [...], # 位宽不匹配警告 +# 'logic_issues': [...], # 逻辑问题 +# 'syntax_warnings': [...], # 语法警告 +# 'should_retry': bool # 是否建议重试 +# } +# """ +# result = { +# 'width_mismatch': [], +# 'logic_issues': [], +# 'syntax_warnings': [], +# 'should_retry': False +# } + +# # 检查位宽不匹配 +# result['width_mismatch'] = self._check_width_mismatch(code, signal_widths) + +# # 检查逻辑问题 +# result['logic_issues'] = self._check_logic_issues(code, signal_widths) + +# # 检查其他语法问题(传入已声明信号) +# result['syntax_warnings'] = self._check_syntax_warnings(code, declared_signals) + +# # 决定是否需要重试 +# # 包括:位宽不匹配、逻辑问题、语法错误(severity='error') +# has_syntax_errors = any( +# issue.get('severity') == 'error' +# for issue in result['syntax_warnings'] +# ) +# result['should_retry'] = ( +# len(result['width_mismatch']) > 0 or +# len(result['logic_issues']) > 0 or +# has_syntax_errors +# ) + +# return result + +# def _check_width_mismatch(self, code: str, signal_widths: Dict[str, int] = None) -> List[Dict]: +# """ +# 检测位宽不匹配问题 + +# 常见问题: +# - {signal} = N'b... 将多位值赋给单比特信号 +# - signal = N'b... 位宽不匹配 +# """ +# issues = [] +# signal_widths = signal_widths or {} + +# # 默认假设未声明信号为 1 位 +# def get_width(sig): +# return signal_widths.get(sig, 1) + +# # 模式1: {signal} = N'bvalue (拼接赋值) +# # 例: {in} = 8'b01111100 - 将 8 位赋给 1 位 +# concat_pattern = re.compile(r'\{(\w+)\}\s*=\s*(\d+)\'([bhd])([0-9a-fA-FxXzZ_]+)') +# for match in concat_pattern.finditer(code): +# signal = match.group(1) +# value_width = int(match.group(2)) +# base = match.group(3) +# value = match.group(4) + +# actual_width = get_width(signal) + +# if value_width > actual_width: +# issues.append({ +# 'type': 'concat_width_mismatch', +# 'signal': signal, +# 'signal_width': actual_width, +# 'assigned_width': value_width, +# 'original': match.group(0), +# 'message': f"Signal '{signal}' is {actual_width}-bit, but assigned {value_width}-bit value via concatenation. Verilog will truncate.", +# 'severity': 'warning', +# 'suggestion': f"Use a shift register: reg [{value_width-1}:0] temp; temp = {value_width}'{base}{value}; then shift bits one by one" +# }) + +# # 模式2: signal = N'bvalue (直接赋值) +# assign_pattern = re.compile(r'\b(\w+)\s*=\s*(\d+)\'([bhd])([0-9a-fA-FxXzZ_]+)') +# for match in assign_pattern.finditer(code): +# signal = match.group(1) +# value_width = int(match.group(2)) + +# # 跳过拼接赋值(已处理) +# if f'{{{signal}}}' in match.group(0): +# continue + +# actual_width = get_width(signal) + +# # 只有当信号已知且位宽不匹配时才警告 +# if signal in signal_widths and value_width > actual_width: +# issues.append({ +# 'type': 'direct_width_mismatch', +# 'signal': signal, +# 'signal_width': actual_width, +# 'assigned_width': value_width, +# 'original': match.group(0), +# 'message': f"Signal '{signal}' is {actual_width}-bit, but assigned {value_width}-bit value. Truncation will occur.", +# 'severity': 'warning' +# }) + +# return issues + +# def _check_logic_issues(self, code: str, signal_widths: Dict[str, int] = None) -> List[Dict]: +# """ +# 检测逻辑问题 + +# 常见问题: +# - 单比特信号自移位 (in = in >> 1 无效果) +# - 无效的循环条件 +# """ +# issues = [] +# signal_widths = signal_widths or {} + +# def get_width(sig): +# return signal_widths.get(sig, 1) + +# # 模式: signal = signal >> N 或 signal = signal << N +# shift_pattern = re.compile(r'\b(\w+)\s*=\s*\1\s*(>>|<<)\s*(\d+)?') +# for match in shift_pattern.finditer(code): +# signal = match.group(1) +# direction = match.group(2) +# shift_amount = int(match.group(3)) if match.group(3) else 1 + +# actual_width = get_width(signal) + +# if actual_width == 1: +# issues.append({ +# 'type': 'single_bit_shift', +# 'signal': signal, +# 'direction': direction, +# 'original': match.group(0), +# 'message': f"Single-bit signal '{signal}' self-shift has no effect. Result is always 0.", +# 'severity': 'warning', +# 'suggestion': f"Use a shift register for bit-serial input, not the input signal itself" +# }) + +# # 模式: repeat(N) begin ... signal = signal >> 1; end (循环移位单比特) +# repeat_shift_pattern = re.compile(r'repeat\s*\(\s*\d+\s*\)\s*begin[^}]*?(\w+)\s*=\s*\1\s*(>>|<<)', re.DOTALL) +# for match in repeat_shift_pattern.finditer(code): +# signal = match.group(1) +# actual_width = get_width(signal) + +# if actual_width == 1: +# issues.append({ +# 'type': 'repeat_single_bit_shift', +# 'signal': signal, +# 'original': match.group(0)[:100] + '...', +# 'message': f"Repeat loop shifting single-bit signal '{signal}' is ineffective", +# 'severity': 'warning' +# }) + +# return issues + +# def _check_syntax_warnings(self, code: str, declared_signals: set = None) -> List[Dict]: +# """ +# 检测其他语法问题 + +# Args: +# code: 待检查的代码 +# declared_signals: 已声明的信号集合 (从完整 TB 中提取) +# """ +# issues = [] +# declared_signals = declared_signals or set() + +# # 检查: 缺少分号 +# # 注意: 这只是简单检查,不是完整解析 +# lines = code.split('\n') +# for i, line in enumerate(lines): +# stripped = line.strip() +# if not stripped or stripped.startswith('//'): +# continue + +# # 跳过不需要分号的行 +# skip_patterns = [ +# r'^begin$', r'^end$', r'^endcase$', r'^endmodule$', +# r'^else$', r'^\)$', r'^\}\s*$', r'^`timescale', r'^`include' +# ] +# if any(re.match(p, stripped) for p in skip_patterns): +# continue + +# # 检查是否需要分号但没有 +# needs_semicolon = re.search(r'\b(initial|always|assign|reg|wire|parameter|localport)\b', stripped) is None +# has_semicolon = stripped.endswith(';') or stripped.endswith(')') or stripped.endswith('}') + +# if needs_semicolon and not has_semicolon and not stripped.endswith('begin'): +# # 可能缺少分号(但不确定) +# pass # 暂不报警,避免误报 + +# # 检查: 不匹配的 begin/end +# begin_count = len(re.findall(r'\bbegin\b', code)) +# end_count = len(re.findall(r'\bend\b', code)) +# if begin_count != end_count: +# issues.append({ +# 'type': 'mismatched_begin_end', +# 'message': f"Mismatched begin/end: {begin_count} begin vs {end_count} end", +# 'severity': 'error' +# }) + +# # 检查: 未声明的信号(在赋值左侧使用的信号) +# # 使用传入的已声明信号集合 +# for match in re.finditer(r'^\s*(\w+)\s*=', code, re.MULTILINE): +# signal = match.group(1) +# # 跳过系统任务和关键字 +# if signal in ['if', 'else', 'case', 'for', 'while', 'repeat', 'assign', 'force', 'release']: +# continue +# # 跳过以 $ 开头的系统任务 +# if signal.startswith('$'): +# continue +# # 检查是否在已声明信号列表中 +# if signal not in declared_signals: +# issues.append({ +# 'type': 'undeclared_signal', +# 'signal': signal, +# 'message': f"Signal '{signal}' is used but not declared in the testbench", +# 'severity': 'error', +# 'suggestion': f"Use an existing signal name (declared: {', '.join(list(declared_signals)[:10])}...)" +# }) + +# # 检查: always 块与时钟生成冲突 +# # 检测是否有多个 always/initial 块驱动同一信号 +# always_blocks = re.findall(r'\balways\s*(@[^i]|begin)', code) +# initial_clk_blocks = len(re.findall(r'initial\s+begin[^i]*?clk\s*=', code, re.DOTALL)) +# always_clk_blocks = len(re.findall(r'\balways[^i]*?clk\s*=', code, re.DOTALL)) + +# if initial_clk_blocks > 0 and always_clk_blocks > 0: +# issues.append({ +# 'type': 'multiple_clock_drivers', +# 'message': f"Multiple clock drivers detected: {initial_clk_blocks} initial + {always_clk_blocks} always blocks driving clk", +# 'severity': 'error', +# 'suggestion': "Remove duplicate clock generation. The testbench already has clock generation." +# }) + +# # 检查: initial 块嵌套(生成了 initial begin ... end 在注入时会导致嵌套) +# if re.search(r'\binitial\s+begin\b', code): +# issues.append({ +# 'type': 'initial_block_injection', +# 'message': "Code contains 'initial begin...end' block which should not be injected into an existing initial block", +# 'severity': 'error', +# 'suggestion': "Remove the 'initial begin...end' wrapper, keep only the test statements inside" +# }) + +# return issues + + +# # ============================================================================ +# # CoverageParser - 覆盖率解析器 +# # ============================================================================ +# class CoverageParser: +# """覆盖率解析器 - 从带注释的Verilog文件中提取未覆盖的代码块 + +# [增强] 集成语义分析结果,提供更精准的 FSM 状态路径指导 +# [新增] 集成能量分配层,提供目标功能点优先级信息 +# [新增] 集成多样性约束注入器,避免测试用例同质化 +# """ + +# def __init__(self, annotated_file, tb_code=None, semantic_result=None, +# energy_allocator=None, diversity_injector=None): +# self.file_path = annotated_file +# self.tb_code = tb_code +# self.semantic_result = semantic_result # [新增] 语义分析结果 +# self.energy_allocator = energy_allocator # [新增] 能量分配器 +# self.diversity_injector = diversity_injector # [新增] 多样性约束注入器 +# # 修复:Verilator 覆盖率标记格式多样化: +# # %NNNNNN - 行覆盖计数(%000000 表示从未执行) +# # ~NNNNNN - 分支/条件覆盖计数(~000000 表示分支从未执行) +# # ^NNNNNN - 未覆盖的分支标记 +# # NNNNNN - 空格开头+数字(某些 Verilator 版本) +# # NNNNNN - 纯数字开头(无前缀) +# self.line_pattern = re.compile(r'^%(\d+)\s+(.*)$') # 匹配 %NNNNNN code +# self.tilde_pattern = re.compile(r'^~(\d+)\s+(.*)$') # 匹配 ~NNNNNN code +# self.caret_pattern = re.compile(r'^\^(\d+)\s+(.*)$') # 匹配 ^NNNNNN code +# # [修复] 纯数字开头(无前缀)或空格开头 +# self.plain_pattern = re.compile(r'^\s*(\d+)\s+(.*)$') # 匹配 " NNNNNN" 或 "NNNNNN" +# self.decl_pattern = re.compile(r'^\s*(input|output|inout|wire|reg|logic|parameter|localparam|assign)\b') + +# self.validator = BlackBoxValidator() +# if tb_code: +# self.validator._extract_signals_from_tb(tb_code) + +# def generate_prompt(self, current_score): +# """生成覆盖率驱动的Prompt""" +# if not os.path.exists(self.file_path): +# return None + +# try: +# with open(self.file_path, 'r', encoding='utf-8', errors='ignore') as f: +# lines = f.readlines() +# except Exception: +# return None + +# missing_blocks = [] +# current_block = [] +# recording = False +# context_buffer = [] +# CONTEXT_SIZE = 3 + +# # 收集缺失行用于 FSM 分析 +# missing_lines = [] + +# for i, line in enumerate(lines): +# line = line.strip() +# count = -1 +# clean_code = line +# is_tilde = False +# is_caret = False + +# # 尝试匹配各种覆盖率标记格式 +# # Verilator 覆盖率格式: +# # - %NNNNNN: 行覆盖,NNNNNN 是执行次数,%000000 表示未执行 +# # - ~NNNNNN: 分支/条件覆盖,~000000 表示分支从未执行 +# # - ^NNNNNN: 未覆盖分支标记 +# # - NNNNNN: 无前缀格式(某些版本) +# match_pct = self.line_pattern.match(line) # %NNNNNN code +# match_tilde = self.tilde_pattern.match(line) # ~NNNNNN code +# match_caret = self.caret_pattern.match(line) # ^NNNNNN code +# match_plain = self.plain_pattern.match(line) # NNNNNN code (无前缀) + +# if match_pct: +# count = int(match_pct.group(1)) +# clean_code = match_pct.group(2).strip() +# elif match_tilde: +# count = int(match_tilde.group(1)) +# clean_code = match_tilde.group(2).strip() +# is_tilde = True +# elif match_caret: +# count = int(match_caret.group(1)) +# clean_code = match_caret.group(2).strip() +# is_caret = True +# elif match_plain: +# # 纯数字格式(可能出现在某些 Verilator 版本) +# count = int(match_plain.group(1)) +# clean_code = match_plain.group(2).strip() + +# if "//" in clean_code: +# clean_code = clean_code.split("//")[0].strip() + +# is_hard_noise = (self.decl_pattern.match(clean_code) or clean_code == "endmodule") +# is_soft_noise = (len(clean_code) < 2 or clean_code in ["end", "begin", "else", ");", "endcase", "default:"] or +# clean_code.startswith("module ") or not any(c.isalnum() for c in clean_code)) + +# # [修改] 覆盖状态判断: +# # - %NNNNNN: count > 0 表示已覆盖,count == 0 表示未覆盖 +# # - ~NNNNNN: 分支覆盖标记,count == 0 也表示未覆盖! +# # - ^NNNNNN: 未覆盖分支标记 +# is_definitely_covered = (not is_tilde and not is_caret and count > 0) +# # [关键修复] tilde 格式 count == 0 也应该被视为 missing +# is_definitely_missed = ( +# (not is_tilde and not is_caret and count == 0 and not is_hard_noise and not is_soft_noise) or +# (is_tilde and count == 0 and not is_hard_noise and not is_soft_noise) or # [新增] ~000000 也是 missing +# (is_caret and not is_hard_noise and not is_soft_noise) +# ) + +# if recording: +# if is_definitely_covered: +# missing_blocks.append(current_block) +# missing_lines.extend(current_block) +# current_block = [] +# recording = False +# if not is_hard_noise: +# context_buffer.append(clean_code) +# else: +# if not is_hard_noise and not (is_soft_noise and len(clean_code) < 4): +# current_block.append(f"Line {i+1}: {clean_code}") +# else: +# if is_definitely_missed: +# recording = True +# if context_buffer: +# current_block.append(f"... (Context)") +# for ctx in context_buffer: +# current_block.append(f" {ctx}") +# current_block.append(f"Line {i+1}: {clean_code} <--- MISSING START") +# else: +# if not is_hard_noise and not (is_soft_noise and len(clean_code) < 4): +# context_buffer.append(clean_code) +# if len(context_buffer) > CONTEXT_SIZE: +# context_buffer.pop(0) + +# if recording and current_block: +# missing_blocks.append(current_block) +# missing_lines.extend(current_block) + +# # [改进] 详细诊断日志 - 使用 info 级别确保可见 +# total_lines = len(lines) +# parsed_lines = sum(1 for l in lines if l.strip() and ( +# self.line_pattern.match(l.strip()) or +# self.tilde_pattern.match(l.strip()) or +# self.caret_pattern.match(l.strip()) or +# self.plain_pattern.match(l.strip()) +# )) + +# # 收集零计数行的详细信息 +# zero_count_details = [] +# for l in lines: +# l_stripped = l.strip() +# if not l_stripped: +# continue +# match_pct = self.line_pattern.match(l_stripped) +# match_tilde = self.tilde_pattern.match(l_stripped) +# if match_pct and int(match_pct.group(1)) == 0: +# zero_count_details.append(('%', match_pct.group(2).strip()[:50])) +# elif match_tilde and int(match_tilde.group(1)) == 0: +# zero_count_details.append(('~', match_tilde.group(2).strip()[:50])) + +# zero_count_lines = len(zero_count_details) + +# logger.info(f"CoverageParser: Total={total_lines}, Parsed={parsed_lines}, Zero-count={zero_count_lines}, Missing blocks={len(missing_blocks)}") + +# if not missing_blocks: +# # [改进] 详细诊断信息 +# if zero_count_lines > 0: +# logger.warning(f"Found {zero_count_lines} lines with zero coverage count, but no missing blocks extracted.") +# logger.warning("Zero-count lines:") +# for prefix, code in zero_count_details[:10]: # 只显示前10个 +# logger.warning(f" {prefix}000000: {code}") +# if len(zero_count_details) > 10: +# logger.warning(f" ... and {len(zero_count_details) - 10} more") +# logger.warning("These lines may have been filtered as noise (declarations, etc.)") +# return None + +# selected_blocks = missing_blocks[:50] + +# # 获取实际信号名用于示例 +# reset_signal = self.validator._find_reset_signal() +# inputs_no_clk = [s for s in self.validator.dut_inputs if 'clk' not in s.lower()] +# example_signal = inputs_no_clk[0] if inputs_no_clk else (reset_signal if reset_signal != "reset" else "ena") + +# # 分析 FSM 相关的缺失代码 +# fsm_analysis = self._analyze_fsm_missing(missing_lines) + +# # [新增] 从语义分析结果获取 FSM 和功能点信息 +# semantic_context = self._generate_semantic_context() + +# prompt = f""" +# [ROLE] +# You are a hardware verification expert. Your task is to write a test scenario to improve code coverage. + +# [COVERAGE STATUS] +# Current testbench achieves {current_score:.2f}% coverage. +# The following logic blocks in the DUT are NEVER executed during simulation: + +# """ +# for idx, block in enumerate(selected_blocks): +# prompt += f"--- Missing Logic Block {idx+1} ---\n" + "\n".join(block) + "\n\n" + +# # [新增] 添加语义分析上下文 +# if semantic_context: +# prompt += f""" +# [SEMANTIC ANALYSIS - MODULE UNDERSTANDING] +# {semantic_context} +# """ + +# # === [新增] 添加能量分配目标上下文 === +# if self.energy_allocator: +# energy_context = self.energy_allocator.get_target_context() +# if energy_context: +# prompt += f""" +# [ENERGY-ALIGNED TARGET - PRIORITY] +# {energy_context} +# Focus your test scenario on covering this high-priority target first. +# """ +# # ===================================== + +# prompt += self.validator.generate_constraint_prompt() + +# # 添加 FSM 分析提示 +# if fsm_analysis: +# prompt += f""" +# [FSM STATE TRANSITION ANALYSIS - CRITICAL] +# {fsm_analysis} + +# IMPORTANT: FSM transitions have PRIORITY ORDER! +# - 'if' conditions are evaluated TOP to BOTTOM +# - The FIRST matching condition determines the next state +# - To trigger a branch like "else if (condition)", you MUST ensure all higher-priority conditions are FALSE +# - Read the missing code's context carefully: what conditions precede it? + +# """ + +# prompt += f""" +# [OUTPUT REQUIREMENTS - CRITICAL] +# 1. Return ONLY Verilog test scenario code (NOT a task definition) +# 2. Your code will be inserted INTO an existing `initial begin ... end` block +# 3. DO NOT wrap your code in `task ... endtask` - just write the test sequence directly +# 4. DO NOT use `$finish` or `$stop` - the testbench handles simulation end + +# [CODING STYLE] +# 1. Use blocking assignments for input signals: `signal = value;` +# 2. Use `#N;` for time delays: `#10;` means wait 10 time units +# 3. Use `repeat(N) @(posedge clk);` to wait for N clock cycles +# 4. Start with reset sequence if needed + +# [BLACK-BOX CONSTRAINTS - CRITICAL] +# 1. You can ONLY control module INPUTS listed above +# 2. You CANNOT access internal signals (state, next_state, counters, etc.) +# 3. You CANNOT use `force` or `assign` on internal signals +# 4. To trigger a specific state: drive inputs and wait for the FSM to reach it naturally + +# [STEP-BY-STEP APPROACH - REQUIRED] +# For each missing branch, think through: +# 1. What STATE must the FSM be in? (Look at the case statement) +# 2. What CONDITIONS must be true/false? (Check priority order!) +# 3. How to reach that state from reset? (Trace state transitions) +# 4. What inputs to apply and in what order? + +# [POSITIVE EXAMPLE - CORRECT APPROACH] +# ```verilog +# // Reset sequence - use ACTUAL input signal names from above +# {reset_signal} = 1; +# repeat(2) @(posedge clk); +# {reset_signal} = 0; + +# // Wait for FSM to reach desired state (estimate cycles) +# repeat(3) @(posedge clk); + +# // Trigger missing branch by driving inputs +# {example_signal} = 1; +# repeat(5) @(posedge clk); +# {example_signal} = 0; +# repeat(10) @(posedge clk); +# ``` + +# [NEGATIVE EXAMPLE - DO NOT DO THIS] +# ```verilog +# // WRONG: Using wrong signal name (e.g., 'reset' instead of '{reset_signal}') +# reset = 1; // ERROR: Signal 'reset' does not exist! Use '{reset_signal}' instead! + +# // WRONG: Not considering condition priority in FSM +# // If missing code is "else if (condition_b)", you must make condition_a FALSE first! +# // Example: if FSM has "if (!signal_a) ... else if (signal_b) ..." +# // Then signal_a must be 1 (FALSE) for the else-if branch to execute +# signal_a = 0; // WRONG: This blocks the else-if branch! +# signal_b = 1; // This will NOT trigger because signal_a=0 took priority + +# // CORRECT: Analyze priority, set higher-priority conditions to FALSE +# signal_a = 1; // Now the first condition (!signal_a) is FALSE +# signal_b = 1; // Now this else-if branch can execute + +# // WRONG: Trying to assign internal state +# state = IDLE; // ERROR: Cannot modify internal signal! + +# // WRONG: Using force on internal signal +# force DUT.state = WL; // ERROR: Cannot force internal signal! + +# // WRONG: Checking internal state in condition +# if (state == WL) begin // ERROR: Cannot read internal signal! +# {example_signal} = 1; +# end + +# // CORRECT ALTERNATIVE: Estimate timing instead +# repeat(5) @(posedge clk); // Wait for FSM to reach expected state +# {example_signal} = 1; +# ``` + +# [SIGNAL NAME WARNING - CRITICAL] +# - DO NOT use 'reset' if the actual signal is '{reset_signal}' +# - DO NOT use 'rst' if the actual signal is '{reset_signal}' +# - ALWAYS use EXACT signal names from the ALLOWED INPUTS list above +# - Double-check every signal name before using it! + +# Now write the test scenario code to cover the missing blocks: +# """ + +# # === [新增] 注入多样性约束 === +# if self.diversity_injector: +# # 获取未覆盖功能点 +# uncovered_functions = [] +# if self.semantic_result and self.semantic_result.get('function_points'): +# uncovered_functions = [ +# fp for fp in self.semantic_result['function_points'] +# if not fp.get('covered', False) +# ] + +# # 获取当前目标功能点 +# target_function = "" +# if self.energy_allocator and self.energy_allocator.current_target: +# target_function = self.energy_allocator.current_target.function_point + +# # 注入多样性约束 +# prompt = self.diversity_injector.inject_diversity_constraints( +# prompt=prompt, +# target_function=target_function, +# uncovered_functions=uncovered_functions +# ) +# # ================================= + +# return prompt + +# def _analyze_fsm_missing(self, missing_lines: List[str]) -> str: +# """分析 FSM 相关的缺失代码,生成具体的 FSM 状态转换指导""" +# analysis = [] + +# # 检查是否涉及 FSM 状态转换 +# has_state_case = any('case' in line.lower() and 'state' in line.lower() for line in missing_lines) +# has_else_if = any('else if' in line.lower() for line in missing_lines) +# has_if_condition = any(re.search(r'\bif\s*\(', line) for line in missing_lines) + +# if has_state_case or has_else_if: +# analysis.append("- Missing code involves FSM state transitions or conditional branches") + +# if has_else_if or has_if_condition: +# analysis.append("- Conditional branches have PRIORITY ORDER (top to bottom)") +# analysis.append("- 'else if' branches require ALL previous conditions to be FALSE") +# analysis.append("- Analyze the missing code's context: what conditions block this branch?") + +# if has_state_case: +# analysis.append("- To trigger a state transition: first reach the source state, then drive inputs") + +# # === 新增:FSM 状态路径分析 === +# # 尝试从缺失代码中提取 FSM 状态信息 +# fsm_state_info = self._extract_fsm_state_from_missing(missing_lines) +# if fsm_state_info: +# analysis.append("") +# analysis.append("[FSM STATE PATH ANALYSIS]") +# analysis.extend(fsm_state_info) + +# return "\n".join(analysis) if analysis else "" + +# def _extract_fsm_state_from_missing(self, missing_lines: List[str]) -> List[str]: +# """ +# 从缺失代码中提取 FSM 状态信息,生成具体的状态转换指导 + +# 分析策略: +# 1. 从缺失代码的上下文识别 case 分支(FSM 状态) +# 2. 分析该状态下的条件分支优先级 +# 3. 识别需要满足的输入条件 +# """ +# info = [] + +# # 从 annotated 文件中读取完整的 DUT 代码以分析 FSM 结构 +# try: +# with open(self.file_path, 'r', encoding='utf-8', errors='ignore') as f: +# full_content = f.read() +# except: +# return info + +# # 提取缺失代码所在的 FSM 状态 +# target_state = None +# missing_condition = None + +# for line in missing_lines: +# # 查找 case 分支标记(如 "WL:", "WR:", "FALLL:" 等) +# # 格式可能是 "Line N: STATE:" 或 "STATE:" +# state_match = re.search(r'\b([A-Z][A-Z0-9_]*)\s*:', line) +# if state_match: +# potential_state = state_match.group(1) +# # 排除常见的非状态关键字 +# if potential_state not in ['IF', 'ELSE', 'CASE', 'BEGIN', 'END', 'DEFAULT']: +# target_state = potential_state +# break + +# # 如果没找到,尝试从整个文件中分析 +# if not target_state: +# # 查找缺失行附近的 case 分支 +# lines = full_content.split('\n') +# for i, line in enumerate(lines): +# # 查找覆盖率标记为 0 的行 +# if re.match(r'^%000000', line.strip()): +# # 向上查找最近的 case 分支(状态) +# for j in range(i-1, max(0, i-20), -1): +# state_match = re.search(r'^\s*([A-Z][A-Z0-9_]*)\s*:', lines[j]) +# if state_match: +# target_state = state_match.group(1) +# break +# if target_state: +# break + +# # 分析缺失的条件分支 +# for line in missing_lines: +# # 提取 else if 条件 +# else_if_match = re.search(r'else\s+if\s*\(([^)]+)\)', line) +# if else_if_match: +# missing_condition = else_if_match.group(1) +# break +# # 提取 if 条件 +# if_match = re.search(r'\bif\s*\(([^)]+)\)', line) +# if if_match: +# missing_condition = if_match.group(1) +# break + +# # 生成具体的指导信息 +# if target_state: +# info.append(f"- Target FSM state identified: {target_state}") + +# # 查找复位后的初始状态 +# reset_state = self._find_reset_state(full_content) +# if reset_state: +# info.append(f"- After reset, FSM starts in state: {reset_state}") + +# if reset_state != target_state: +# info.append(f"- CRITICAL: You must FIRST transition from {reset_state} to {target_state}!") +# info.append(f"- Do NOT assume FSM will automatically reach {target_state}!") + +# # 尝试找到状态转换路径 +# transition_hint = self._find_state_transition_hint(full_content, reset_state, target_state) +# if transition_hint: +# info.append(f"- To reach {target_state}: {transition_hint}") + +# if missing_condition: +# info.append(f"- Missing condition: \"{missing_condition}\"") +# # 分析条件优先级 +# priority_info = self._analyze_condition_priority(full_content, target_state, missing_condition) +# if priority_info: +# info.extend(priority_info) + +# return info + +# def _find_reset_state(self, content: str) -> Optional[str]: +# """从 DUT 代码中找到复位后的初始状态""" +# # 查找复位逻辑中的状态赋值 +# # 常见模式: if (reset) state <= IDLE; 或 state <= 0; +# patterns = [ +# r'if\s*\([^)]*reset[^)]*\)\s*state\s*<=\s*([A-Z][A-Z0-9_]*);', +# r'if\s*\([^)]*reset[^)]*\)\s*state\s*<=\s*(\d+);', +# r'if\s*\([^)]*rst[^)]*\)\s*state\s*<=\s*([A-Z][A-Z0-9_]*);', +# ] + +# for pattern in patterns: +# match = re.search(pattern, content, re.IGNORECASE) +# if match: +# state = match.group(1) +# # 如果是数字,尝试从参数中找对应的状态名 +# if state.isdigit(): +# # 查找参数定义 +# param_match = re.search(r'parameter\s+([^;]+);', content) +# if param_match: +# params = param_match.group(1) +# # 解析参数列表 +# for param in params.split(','): +# param = param.strip() +# if '=' in param: +# name, value = param.split('=') +# if value.strip() == state: +# return name.strip() +# return state + +# return None + +# def _find_state_transition_hint(self, content: str, from_state: str, to_state: str) -> Optional[str]: +# """找到从一个状态到另一个状态的转换条件""" +# # 在 case 语句中查找 from_state 分支 +# # 提取该分支下到 to_state 的转换条件 + +# # 简单策略:查找 "next = TO_STATE" 或 "next <= TO_STATE" +# pattern = rf'{from_state}\s*:.*?next\s*=?\s*{to_state}' +# match = re.search(pattern, content, re.DOTALL) + +# if match: +# # 提取条件 +# branch_code = match.group(0) +# # 查找 if 条件 +# if_match = re.search(r'if\s*\(([^)]+)\)\s*next\s*=?\s*' + to_state, branch_code) +# if if_match: +# return f"set condition: {if_match.group(1)}" + +# # 查找 else if 条件 +# elif_match = re.search(r'else\s+if\s*\(([^)]+)\)\s*next\s*=?\s*' + to_state, branch_code) +# if elif_match: +# return f"set condition: {elif_match.group(1)} (ensure earlier conditions are FALSE)" + +# # 尝试反向查找:什么条件下会转换到目标状态 +# trans_pattern = rf'(?:if|else\s+if)\s*\(([^)]+)\)\s*(?:next\s*=?\s*{to_state}|{to_state}\s*;)' +# trans_match = re.search(trans_pattern, content) +# if trans_match: +# return f"set condition: {trans_match.group(1)}" + +# return None + +# def _analyze_condition_priority(self, content: str, state: str, missing_condition: str) -> List[str]: +# """分析条件分支的优先级,找出需要排除的条件""" +# info = [] + +# if not state: +# return info + +# # 查找该状态下的所有条件分支 +# # 提取 state: 后面的代码块 +# state_block_pattern = rf'{state}\s*:(.*?)(?=[A-Z][A-Z0-9_]*\s*:|endcase|default:)' +# match = re.search(state_block_pattern, content, re.DOTALL) + +# if not match: +# return info + +# state_block = match.group(1) + +# # 提取所有条件分支 +# conditions = [] +# for cond_match in re.finditer(r'(?:if|else\s+if)\s*\(([^)]+)\)', state_block): +# conditions.append(cond_match.group(1).strip()) + +# # 找到缺失条件在列表中的位置 +# missing_idx = -1 +# for i, cond in enumerate(conditions): +# # 简化比较(去除空格) +# if cond.replace(' ', '') in missing_condition.replace(' ', '') or \ +# missing_condition.replace(' ', '') in cond.replace(' ', ''): +# missing_idx = i +# break + +# if missing_idx > 0: +# info.append(f"- This branch is condition #{missing_idx + 1} (lower priority)") +# info.append(f"- You must make ALL earlier conditions FALSE:") +# for i in range(missing_idx): +# cond = conditions[i] +# # 分析如何使条件为 FALSE +# false_hint = self._get_false_hint(cond) +# info.append(f" * \"{cond}\" must be FALSE → {false_hint}") + +# return info + +# def _get_false_hint(self, condition: str) -> str: +# """分析如何使条件为 FALSE""" +# condition = condition.strip() + +# # 处理 !signal 形式 +# if condition.startswith('!'): +# signal = condition[1:].strip() +# return f"set {signal} = 1" + +# # 处理 signal 形式(布尔值) +# if re.match(r'^[a-zA-Z_][a-zA-Z0-9_]*$', condition): +# return f"set {condition} = 0" + +# # 处理比较运算符 +# if '==' in condition: +# parts = condition.split('==') +# if len(parts) == 2: +# signal = parts[0].strip() +# value = parts[1].strip() +# if value.isdigit(): +# return f"set {signal} != {value}" + +# # 处理 >= 形式 +# if '>=' in condition: +# parts = condition.split('>=') +# if len(parts) == 2: +# signal = parts[0].strip() +# value = parts[1].strip() +# if value.isdigit(): +# return f"set {signal} < {value}" + +# # 处理 > 形式 +# if '>' in condition and '>=' not in condition: +# parts = condition.split('>') +# if len(parts) == 2: +# signal = parts[0].strip() +# value = parts[1].strip() +# return f"set {signal} <= {value}" + +# return "analyze the condition logic" + +# def _generate_semantic_context(self) -> str: +# """ +# [新增] 从语义分析结果生成 Prompt 上下文 + +# 整合语义分析层 (Layer 0) 的输出,为 LLM 提供更精准的指导: +# - FSM 状态转换图 +# - 功能点重要性排序 +# - 测试场景建议 + +# Returns: +# 语义上下文字符串,用于增强 Prompt +# """ +# if not self.semantic_result: +# return "" + +# context_parts = [] + +# # 1. 模块基础信息 +# module_name = self.semantic_result.get('module_name', '') +# inputs = self.semantic_result.get('inputs', []) +# outputs = self.semantic_result.get('outputs', []) + +# if module_name: +# context_parts.append(f"Module Name: {module_name}") +# if inputs: +# context_parts.append(f"Module Inputs: {', '.join(inputs)}") +# if outputs: +# context_parts.append(f"Module Outputs: {', '.join(outputs)}") + +# # 2. FSM 信息(最关键) +# fsm_info = self.semantic_result.get('fsm_info') +# if fsm_info: +# context_parts.append("") +# context_parts.append("=== FSM STATE MACHINE DETAILS ===") +# context_parts.append(f"State Variable: {fsm_info.get('state_variable', 'unknown')}") + +# states = fsm_info.get('states', []) +# if states: +# context_parts.append(f"All States ({len(states)}): {', '.join(states)}") + +# # 状态转换表 +# transitions = fsm_info.get('transitions', {}) +# if transitions: +# context_parts.append("") +# context_parts.append("=== STATE TRANSITION TABLE ===") +# context_parts.append("Format: CURRENT_STATE --[CONDITION]--> NEXT_STATE") +# context_parts.append("") + +# for state, trans_list in transitions.items(): +# for trans in trans_list: +# condition = trans.get('condition', 'default') +# next_state = trans.get('next_state', 'unknown') +# if condition == 'default': +# context_parts.append(f" {state} --[default]--> {next_state}") +# else: +# context_parts.append(f" {state} --[if ({condition})]--> {next_state}") + +# # 添加状态转换路径分析 +# context_parts.append("") +# context_parts.append("=== STATE TRANSITION PATH HINTS ===") +# reset_state = self._find_reset_state_from_fsm(fsm_info) +# if reset_state: +# context_parts.append(f"Initial State (after reset): {reset_state}") +# context_parts.append("") +# context_parts.append("IMPORTANT: To reach a target state, trace the path from reset:") +# context_parts.append(" 1. Reset the DUT to initialize to the starting state") +# context_parts.append(" 2. Apply inputs to trigger state transitions") +# context_parts.append(" 3. Wait for the FSM to naturally reach the target state") +# context_parts.append(" 4. THEN apply inputs to trigger the missing branch") + +# # 3. 功能点优先级 +# function_points = self.semantic_result.get('function_points', []) +# if function_points: +# context_parts.append("") +# context_parts.append("=== FUNCTION POINTS (Ranked by Importance) ===") + +# for i, fp in enumerate(function_points[:10]): # Top 10 +# name = fp.get('name', 'unknown') +# fp_type = fp.get('type', 'unknown') +# importance = fp.get('importance', 0) +# covered = fp.get('covered', False) +# status = "✓ COVERED" if covered else "✗ NOT COVERED" +# context_parts.append(f" {i+1}. [{status}] {name} ({fp_type}): importance={importance:.2f}") + +# # 4. 测试场景建议 +# test_scenarios = self.semantic_result.get('test_scenarios', []) +# if test_scenarios: +# context_parts.append("") +# context_parts.append("=== RECOMMENDED TEST SCENARIOS ===") + +# for i, ts in enumerate(test_scenarios[:5]): # Top 5 +# name = ts.get('name', 'unknown') +# description = ts.get('description', '') +# priority = ts.get('priority', 0) +# context_parts.append(f" {i+1}. {name}: {description} (priority={priority:.2f})") + +# if context_parts: +# return "\n".join(context_parts) +# return "" + +# def _find_reset_state_from_fsm(self, fsm_info: dict) -> Optional[str]: +# """从 FSM 信息中推断复位后的初始状态""" +# # 方法1:检查是否有明确的复位状态 +# transitions = fsm_info.get('transitions', {}) + +# # 复位后通常进入第一个定义的状态或特定名称的状态 +# states = fsm_info.get('states', []) + +# # 常见的初始状态命名 +# initial_state_names = ['IDLE', 'INIT', 'RESET', 'START', 'BEGIN'] + +# for name in initial_state_names: +# if name in states: +# return name + +# # 如果没有找到,返回第一个状态 +# if states: +# return states[0] + +# return None + + +# # ============================================================================ +# # TBInjector - 场景注入器 +# # ============================================================================ +# class TBInjector: +# """ +# 场景注入器 - 将LLM生成的测试代码注入到现有测试平台 + +# 集成三层防护策略: +# 1. Layer 1: Prompt约束(由CoverageParser处理) +# 2. Layer 2: 智能代码转换 +# 3. Layer 3: 质量评估和重试建议 +# """ + +# def __init__(self, tb_code): +# """ +# 初始化注入器 + +# Args: +# tb_code: 原始测试平台代码字符串 +# """ +# self.content = tb_code +# self.validator = BlackBoxValidator() +# self.validator._extract_signals_from_tb(tb_code) +# self.last_validation_result = None + +# def inject(self, new_code, iter_idx): +# """ +# 注入新的测试场景到测试平台 + +# Args: +# new_code: LLM生成的测试代码 +# iter_idx: 迭代序号 + +# Returns: +# 修改后的测试平台代码 +# """ +# # Step 1: 预处理代码(包含三层防护) +# scenario_code, result = self._preprocess_code(new_code, iter_idx) + +# self.last_validation_result = result + +# # 记录日志 +# if result['violations']['critical']: +# logger.warning(f"[CGA-{iter_idx}] Critical violations detected:") +# for v in result['violations']['critical']: +# logger.warning(f" - {v}") + +# if result['violations']['warning']: +# logger.info(f"[CGA-{iter_idx}] Warnings:") +# for v in result['violations']['warning']: +# logger.info(f" - {v}") + +# if result['transformations']: +# logger.info(f"[CGA-{iter_idx}] Code transformations applied:") +# for t in result['transformations']: +# logger.info(f" - {t['type']}: {t.get('original', 'N/A')[:50]}...") + +# # Step 2: 构建场景块 +# scenario_block = self._build_scenario_block(scenario_code, iter_idx) + +# # Step 3: 注入到TB中 +# modified_tb = self._inject_scenario(scenario_block) + +# return modified_tb + +# def should_retry(self): +# """是否应该重试""" +# if self.last_validation_result is None: +# return False +# return self.last_validation_result.get('should_retry', False) + +# def get_quality_score(self): +# """获取代码质量分数""" +# if self.last_validation_result is None: +# return 0 +# return self.last_validation_result.get('quality_score', 0) + +# def _preprocess_code(self, code, iter_idx): +# """预处理LLM生成的代码 - 增强版,包含语法预检查""" +# # 移除markdown标记 +# code = re.sub(r'```(?:verilog|systemverilog|sv)?\n?', '', code) +# code = re.sub(r'```', '', code) + +# # 移除task包装 +# code = re.sub(r'task\s+\w+\s*(?:\([^)]*\))?\s*;', '', code) +# code = re.sub(r'endtask', '', code) + +# # 移除$finish和$stop +# code = re.sub(r'\$finish\s*;', '', code) +# code = re.sub(r'\$stop\s*;', '', code) + +# # 移除多余空行 +# code = re.sub(r'\n\s*\n\s*\n', '\n\n', code) + + +# # [新增] 移除 initial begin ... end 代码块 +# # LLM 可能生成完整的 initial begin...end 块,但我们只需要其中的测试代码 +# initial_pattern = re.compile(r'\binitial\s+begin\b.*?\bend\b', re.DOTALL | re.IGNORECASE) + +# # 检查并移除 initial begin ... end 块 +# initial_match = initial_pattern.search(code) +# if initial_match: +# logger.warning(f"[CGA-{iter_idx}] Detected 'initial begin...end' block in generated code - this should not be included") +# logger.warning(f"[CGA-{iter_idx}] Removing 'initial begin...end' wrapper, keeping only the test content") +# # 提取块内的内容 +# block_content = initial_match.group(0) +# # 移除 initial begin 和 end 包装 +# # 保留块内的实际测试代码 +# inner_content = re.sub(r'^\s*initial\s+begin\s*', '', block_content) +# inner_content = re.sub(r'\bend\s*$', '', inner_content) +# # 替换整个块为内部内容 +# code = initial_pattern.sub(inner_content.strip(), code, count=1) + +# code = re.sub(r'\n\s*\n\s*\n', '\n\n', code) + + +# # 信号名自动修正(在验证之前) +# code = self._auto_correct_signal_names(code) + +# # 三层防护:黑盒约束验证和转换 +# code, result = self.validator.validate_and_transform(code, self.content) + +# # [新增] 第四层:Verilog 语法预检查 +# # 提取完整 TB 中已声明的信号(不只是代码片段) +# signal_widths = self._extract_signal_widths() +# declared_signals = self._extract_declared_signals() + +# # 调用语法检查,传入已声明信号列表 +# syntax_result = self.validator.check_syntax_issues( +# code, +# signal_widths, +# declared_signals=declared_signals +# ) + +# # 合并检查结果 +# result['syntax_check'] = syntax_result + +# # 记录语法问题日志 +# if syntax_result['width_mismatch']: +# logger.warning(f"[CGA-{iter_idx}] Width mismatch detected:") +# for issue in syntax_result['width_mismatch']: +# logger.warning(f" - {issue['message']}") +# if 'suggestion' in issue: +# logger.info(f" Suggestion: {issue['suggestion']}") + +# if syntax_result['logic_issues']: +# logger.warning(f"[CGA-{iter_idx}] Logic issues detected:") +# for issue in syntax_result['logic_issues']: +# logger.warning(f" - {issue['message']}") +# if 'suggestion' in issue: +# logger.info(f" Suggestion: {issue['suggestion']}") + +# if syntax_result['syntax_warnings']: +# for issue in syntax_result['syntax_warnings']: +# if issue['severity'] == 'error': +# logger.error(f"[CGA-{iter_idx}] Syntax error: {issue['message']}") +# else: +# logger.warning(f"[CGA-{iter_idx}] Syntax warning: {issue['message']}") + +# # 如果语法检查发现问题,设置 should_retry +# if syntax_result['should_retry']: +# result['should_retry'] = True +# logger.warning(f"[CGA-{iter_idx}] Syntax issues detected, recommend retry with corrected code") + +# code = re.sub(r'\n\s*\n\s*\n', '\n\n', code) + +# return code.strip(), result + +# def _extract_declared_signals(self) -> set: +# """从完整测试平台中提取所有已声明的信号""" +# signals = set() + +# # 匹配 reg [N:0] signal 或 wire [N:0] signal +# for match in re.finditer(r'\b(reg|wire|logic)\s+(?:\[[^\]]+\]\s*)?(\w+)', self.content): +# signals.add(match.group(2)) + +# # 匹配 input/output 声明 +# for match in re.finditer(r'\b(input|output|inout)\s+(?:\[[^\]]+\]\s*)?(\w+)', self.content): +# signals.add(match.group(2)) + +# # 匹配模块端口连接中的信号 +# for match in re.finditer(r'\.(\w+)\s*\(\s*(\w+)\s*\)', self.content): +# signals.add(match.group(2)) # 添加连接的信号名 + +# return signals + +# def _extract_signal_widths(self) -> Dict[str, int]: +# """从测试平台中提取信号位宽信息""" +# widths = {} + +# # 匹配 reg [N:0] signal 或 wire [N:0] signal +# width_pattern = re.compile(r'\b(reg|wire)\s+\[(\d+):(\d+)\]\s+(\w+)') + +# for match in width_pattern.finditer(self.content): +# high = int(match.group(2)) +# low = int(match.group(3)) +# width = high - low + 1 +# signal = match.group(4) +# widths[signal] = width + +# # 匹配无位宽声明的信号(默认 1 位) +# single_bit_pattern = re.compile(r'\b(reg|wire)\s+(?!.*\[)(\w+)\s*;') +# for match in single_bit_pattern.finditer(self.content): +# signal = match.group(2) +# if signal not in widths: +# widths[signal] = 1 + +# return widths + +# def _auto_correct_signal_names(self, code: str) -> str: +# """自动修正信号名错误""" +# corrections = [] + +# # 获取正确的复位信号名 +# reset_signal = self.validator._find_reset_signal() + +# # 如果正确的复位信号不是 'reset',则修正所有 'reset' 引用 +# if reset_signal != "reset": +# # 匹配独立的 'reset' 单词(不包括 'areset', 'rst_n' 等) +# pattern = r'\breset\b(?!\w)' +# matches = re.findall(pattern, code) +# if matches: +# code = re.sub(pattern, reset_signal, code) +# corrections.append(f"reset -> {reset_signal} ({len(matches)} occurrences)") + +# # 检查是否有使用 'rst' 但正确信号是 'areset' 的情况 +# if reset_signal == "areset": +# pattern = r'\brst\b(?!\w)' +# matches = re.findall(pattern, code) +# if matches: +# code = re.sub(pattern, reset_signal, code) +# corrections.append(f"rst -> {reset_signal} ({len(matches)} occurrences)") + +# # 检查是否使用了不存在的信号 +# for signal in re.findall(r'\b(\w+)\s*=', code): +# signal = signal.strip() +# # 跳过已知的合法信号 +# if signal in self.validator.dut_inputs: +# continue +# # 检查是否是复位信号的别名 +# if signal.lower() in ['reset', 'rst', 'rst_n', 'rst_b'] and reset_signal != signal: +# code = re.sub(rf'\b{signal}\b', reset_signal, code) +# corrections.append(f"{signal} -> {reset_signal}") + +# if corrections: +# logger.info(f"[Signal Correction] Applied corrections: {'; '.join(corrections)}") + +# return code + +# def _build_scenario_block(self, scenario_code, iter_idx): +# """构建完整的场景代码块""" +# # 格式化缩进 +# lines = scenario_code.split('\n') +# formatted_lines = [] +# for line in lines: +# stripped = line.strip() +# if stripped: +# formatted_lines.append(f" {stripped}") +# formatted_code = '\n'.join(formatted_lines) + +# # 检测输出信号用于日志 +# output_signals = self._detect_output_signals() +# output_log = self._generate_output_log(output_signals, iter_idx) + +# # 构建完整块 +# block = f''' +# // ========== CGA Iteration {iter_idx} ========== +# scenario = 100 + {iter_idx}; +# // Reset signals to safe state +# {self._generate_signal_reset()} +# #5; +# // CGA generated test sequence: +# {formatted_code} +# // Log results +# {output_log} +# // ============================================== +# ''' +# return block + +# def _detect_output_signals(self): +# """检测DUT的输出信号""" +# outputs = [] +# wire_pattern = re.compile(r'wire\s+(?:\[[\d:]+\]\s*)?(\w+)\s*;') +# for match in wire_pattern.finditer(self.content): +# signal = match.group(1) +# if signal.lower() not in ['clk', 'clock', 'rst', 'reset', 'areset']: +# outputs.append(signal) +# return outputs + +# def _generate_signal_reset(self): +# """生成信号重置代码""" +# inputs = [] +# reg_pattern = re.compile(r'reg\s+(?:\[[\d:]+\]\s*)?(\w+)\s*;') +# for match in reg_pattern.finditer(self.content): +# signal = match.group(1) +# if signal.lower() not in ['clk', 'clock', 'file', 'scenario']: +# inputs.append(signal) + +# if inputs: +# return " " + "; ".join([f"{sig} = 0" for sig in inputs]) + ";" +# return " // No input signals to reset" + +# def _generate_output_log(self, signals, iter_idx): +# """生成输出日志代码""" +# if not signals: +# return f' $display("[CGA-{iter_idx}] Scenario executed");' + +# sig_names = ", ".join(signals) +# format_str = ", ".join(["%b"] * len(signals)) + +# return f' $fdisplay(file, "[CGA-{iter_idx}] {sig_names} = {format_str}", {sig_names});' + +# def _inject_scenario(self, scenario_block): +# """将场景块注入到测试平台""" +# modified_tb = self.content + +# # 策略:如果有 $fclose,在其之前插入 +# if "$fclose" in modified_tb: +# modified_tb = re.sub( +# r'(\s*)(\$fclose\s*\([^)]+\)\s*;)', +# scenario_block + r'\1\2', +# modified_tb, +# count=1 +# ) +# elif "$finish" in modified_tb: +# # 否则在 $finish 之前插入 +# modified_tb = modified_tb.replace( +# "$finish;", +# scenario_block + "\n $finish;" +# ) +# else: +# # 兜底:在最后一个 end 之前插入 +# last_end = modified_tb.rfind("end") +# if last_end != -1: +# modified_tb = modified_tb[:last_end] + scenario_block + modified_tb[last_end:] + +# return modified_tb +""" +Description : Utils for CGA (CoverageParser & TBInjector) + - Features: Sticky Mode, Smart Noise Filtering (No assign/decls) + - Enhanced: Three-layer protection for black-box constraints + * Layer 1: Enhanced Prompt constraints (prevention) + * Layer 2: Smart code transformation (conversion) + * Layer 3: Quality assessment & retry (fallback) + - Integrated: Diversity Constraint Injector (Layer 1) +Author : CorrectBench Integration +""" +import re +import os +import logging +from typing import Tuple, List, Optional, Dict, TYPE_CHECKING + +# [新增] 导入多样性约束注入器 +if TYPE_CHECKING: + from autoline.diversity_injector import DiversityInjector + +# 配置日志 +logger = logging.getLogger(__name__) + +# ============================================================================ +# 三层防护策略说明 +# ============================================================================ +# Layer 1 - Prompt约束: 动态提取允许信号列表,明确约束,正反面示例 +# Layer 2 - 智能转换: 检测违规意图,尝试转换为合法形式 +# Layer 3 - 质量评估: 违规比例过高时返回质量分数,触发重新生成 +# ============================================================================ + + +# ============================================================================ +# 黑盒约束检查器 - 三层防护策略实现 +# ============================================================================ +class BlackBoxValidator: + """ + 黑盒约束验证器 - 三层防护策略 + + Layer 1: 增强Prompt约束(预防) + - 动态提取允许信号列表 + - 生成明确的约束提示 + + Layer 2: 智能代码转换(转换) + - 检测违规意图 + - 尝试转换为合法的等价形式 + - 转换失败时才注释 + + Layer 3: 质量评估(重试) + - 计算代码质量分数 + - 违规比例过高时建议重试 + """ + + # 常见的内部信号命名模式(按严重程度分类) + INTERNAL_SIGNAL_PATTERNS = { + # 高风险:FSM状态相关(绝对不能修改) + 'critical': [ + (r'\bstate\b', 'FSM状态寄存器'), + (r'\bnext_state\b', 'FSM下一状态'), + (r'\bcurrent_state\b', 'FSM当前状态'), + (r'\bnext\b(?!\s*[,@])', '下一状态简写'), + ], + # 中风险:计数器和内部寄存器 + 'warning': [ + (r'\bcounter\b', '内部计数器'), + (r'\bcount\b', '计数寄存器'), + (r'\bcnt\b', '计数简写'), + (r'\bfall_counter\b', '下落计数器'), + (r'\breg_\w+', '内部寄存器'), + ], + # 低风险:可疑信号(需要确认) + 'info': [ + (r'\binternal_\w+', '内部信号'), + (r'\btemp_\w+', '临时信号'), + (r'\bprev_\w+', '前一状态'), + ] + } + + # 非法语句模式 + FORBIDDEN_STATEMENTS = [ + (r'\bforce\s+(\w+)', 'force语句', 'critical'), + (r'\bassign\s+(\w+)\s*=', '连续赋值', 'critical'), + (r'\bdeassign\s+', 'deassign语句', 'critical'), + (r'\brelease\s+', 'release语句', 'critical'), + ] + + # 层次化访问模式(如 DUT.state) + HIERARCHICAL_ACCESS = r'(\w+)\s*\.\s*(\w+)' + + # ========================================================================= + # [新增] 通用禁止信号名模式 - LLM 常见的"猜测型"命名 + # ========================================================================= + # 这些模式会被自动检测并禁止,防止 LLM 使用不存在的信号名 + FORBIDDEN_NAME_PATTERNS = [ + # 带数字后缀的通用名 + (r'^input_signal_\d+$', '带数字后缀的 input_signal'), + (r'^input_\d+$', '带数字后缀的 input'), + (r'^in_\d+$', '带数字后缀的 in'), + (r'^output_signal_\d+$', '带数字后缀的 output_signal'), + (r'^output_\d+$', '带数字后缀的 output'), + (r'^out_\d+$', '带数字后缀的 out'), + (r'^data_\d+$', '带数字后缀的 data'), + (r'^data_in_\d+$', '带数字后缀的 data_in'), + (r'^data_out_\d+$', '带数字后缀的 data_out'), + (r'^signal_\d+$', '带数字后缀的 signal'), + (r'^sig_\d+$', '带数字后缀的 sig'), + (r'^port_\d+$', '带数字后缀的 port'), + # 通用控制信号名(除非实际存在) + (r'^reset$', '通用复位名'), + (r'^rst$', '通用复位简写'), + (r'^rst_n$', '通用低电平复位'), + (r'^rst_b$', '通用低电平复位'), + (r'^clr$', '通用清零'), + (r'^clear$', '通用清零'), + (r'^enable$', '通用使能'), + (r'^en$', '通用使能简写'), + (r'^ena$', '通用使能简写'), + (r'^clk_in$', '通用时钟输入'), + (r'^clock$', '通用时钟'), + (r'^clk$', '通用时钟简写'), + # 极简通用名 + (r'^a$', '单字母信号名'), + (r'^b$', '单字母信号名'), + (r'^c$', '单字母信号名'), + (r'^d$', '单字母信号名'), + (r'^x$', '单字母信号名'), + (r'^y$', '单字母信号名'), + (r'^z$', '单字母信号名'), + ] + + def __init__(self, dut_inputs: List[str] = None, dut_outputs: List[str] = None, dut_code: str = None): + """ + Args: + dut_inputs: DUT模块的输入端口列表 + dut_outputs: DUT模块的输出端口列表 + dut_code: DUT模块的源代码(用于提取所有信号名) + """ + self.dut_inputs = dut_inputs or [] + self.dut_outputs = dut_outputs or [] + self.dut_code = dut_code or "" + + # [新增] 从 DUT 代码提取的所有信号名 + self.dut_all_signals = set() # 所有信号名(端口 + 内部信号) + self.dut_internal_signals = set() # 仅内部信号 + self.signal_widths = {} # 信号位宽 + + if dut_code: + self._extract_all_signals_from_dut(dut_code) + + self.violations = {'critical': [], 'warning': [], 'info': []} + self.transformations = [] + + # ========================================================================= + # [新增] 从 DUT 代码提取所有信号名 + # ========================================================================= + def _extract_all_signals_from_dut(self, dut_code: str): + """ + 从 DUT 代码中提取所有信号名(端口 + 内部信号) + + 提取内容: + - 输入端口 (input) + - 输出端口 (output) + - 双向端口 (inout) + - 内部寄存器 (reg) + - 内部连线 (wire) + - 参数 (parameter/localparam) + """ + self.dut_all_signals = set() + self.dut_internal_signals = set() + self.signal_widths = {} + + # 1. 提取端口声明 + # 格式: input/output/inout [width] name + port_patterns = [ + # 带位宽的端口: input [7:0] data_out + (r'(?:^|[\s;,])(input|output|inout)\s+\[(\d+):(\d+)\]\s+(\w+)', 'port'), + # 无位宽的端口: input clk + (r'(?:^|[\s;,])(input|output|inout)\s+(\w+)(?=\s*[;,\n)])', 'port_simple'), + ] + + for pattern, ptype in port_patterns: + for match in re.finditer(pattern, dut_code, re.MULTILINE): + if ptype == 'port': + direction = match.group(1) + msb = int(match.group(2)) + lsb = int(match.group(3)) + name = match.group(4) + width = msb - lsb + 1 + self.dut_all_signals.add(name) + self.signal_widths[name] = width + if direction == 'input' and name not in self.dut_inputs: + self.dut_inputs.append(name) + elif direction == 'output' and name not in self.dut_outputs: + self.dut_outputs.append(name) + else: + direction = match.group(1) + name = match.group(2) + # 排除关键字 + if name.lower() not in ['wire', 'reg', 'logic', 'input', 'output']: + self.dut_all_signals.add(name) + self.signal_widths[name] = 1 + if direction == 'input' and name not in self.dut_inputs: + self.dut_inputs.append(name) + elif direction == 'output' and name not in self.dut_outputs: + self.dut_outputs.append(name) + + # 2. 提取内部信号声明 (reg, wire, logic) + internal_patterns = [ + # 带位宽: reg [7:0] counter + (r'\b(reg|wire|logic)\s+\[(\d+):(\d+)\]\s+(\w+)', 'internal_width'), + # 无位宽: reg state + (r'\b(reg|wire|logic)\s+(\w+)(?=\s*[;,\n=])', 'internal_simple'), + ] + + for pattern, ptype in internal_patterns: + for match in re.finditer(pattern, dut_code): + if ptype == 'internal_width': + sig_type = match.group(1) + msb = int(match.group(2)) + lsb = int(match.group(3)) + name = match.group(4) + width = msb - lsb + 1 + self.dut_all_signals.add(name) + self.dut_internal_signals.add(name) + self.signal_widths[name] = width + else: + sig_type = match.group(1) + name = match.group(2) + # 排除关键字和已提取的端口 + if name.lower() not in ['wire', 'reg', 'logic', 'input', 'output', 'begin', 'end', 'if', 'else', 'case', 'always', 'initial']: + if name not in self.dut_inputs and name not in self.dut_outputs: + self.dut_all_signals.add(name) + self.dut_internal_signals.add(name) + self.signal_widths[name] = 1 + + # 3. 提取参数 + param_pattern = r'(?:parameter|localparam)\s+(?:\[\d+:\d+\]\s*)?(\w+)\s*=' + for match in re.finditer(param_pattern, dut_code): + name = match.group(1) + self.dut_all_signals.add(name) + + logger.info(f"Extracted from DUT: {len(self.dut_inputs)} inputs, {len(self.dut_outputs)} outputs, " + f"{len(self.dut_internal_signals)} internal signals, total {len(self.dut_all_signals)} signals") + + def get_all_allowed_signals(self) -> List[str]: + """获取所有允许的信号名(端口 + 内部信号)""" + return sorted(list(self.dut_all_signals)) + + def get_drivable_signals(self) -> List[str]: + """获取可驱动的信号名(仅输入端口)""" + return sorted(self.dut_inputs) + + def get_readable_signals(self) -> List[str]: + """获取可读取的信号名(输出端口 + 内部信号)""" + return sorted(list(set(self.dut_outputs) | self.dut_internal_signals)) + + def is_valid_signal(self, name: str) -> bool: + """检查信号名是否在 DUT 中存在""" + return name in self.dut_all_signals + + def is_drivable_signal(self, name: str) -> bool: + """检查信号是否可以被驱动(赋值)""" + return name in self.dut_inputs + + def generate_signal_constraint_prompt(self) -> str: + """ + 生成基于 DUT 实际信号名的约束提示 + + 直接告诉 LLM:只能使用这些已存在的信号名 + """ + drivable = self.get_drivable_signals() + readable = self.get_readable_signals() + all_signals = self.get_all_allowed_signals() + + reset_signal = self._find_reset_signal() + + prompt = f""" +{'='*60} +📋 [DUT SIGNAL NAMES - USE ONLY THESE] +{'='*60} + +⚠️ CRITICAL: You can ONLY use signal names that EXIST in the DUT! +⚠️ DO NOT invent or guess any signal name! + +✅ DRIVABLE INPUTS (you CAN assign to these): + {drivable} + +📖 READABLE SIGNALS (you can read but NOT assign): + {readable} + +📝 ALL DUT SIGNALS (for reference): + {all_signals[:20]}{'...' if len(all_signals) > 20 else ''} + +🚫 FORBIDDEN - THESE DO NOT EXIST: + - Any name NOT in the lists above + - "input_signal_1", "input_signal_2", "in_1", "data_1" (generic guesses) + - "reset" (actual: "{reset_signal}"), "rst", "enable", "en" (unless listed above) + +{'='*60} + +⚠️ RULE: If a signal is not listed in DRIVABLE INPUTS, it does NOT exist! + You MUST use exact signal names from the DRIVABLE INPUTS list. + +✅ CORRECT EXAMPLE: + {reset_signal} = 1; // '{reset_signal}' is in DRIVABLE INPUTS + {drivable[0] if drivable else 'signal'} = 0; // Using actual signal name + +❌ WRONG EXAMPLE: + input_signal_1 = 1; // Does NOT exist in DUT! + reset = 1; // Does NOT exist! Use '{reset_signal}' instead! +""" + return prompt + + def validate_and_transform(self, code: str, tb_code: str = None) -> Tuple[str, Dict]: + """验证并转换代码 - 主入口""" + self.violations = {'critical': [], 'warning': [], 'info': []} + self.transformations = [] + + if tb_code: + self._extract_signals_from_tb(tb_code) + + original_lines = code.strip().split('\n') + total_lines = len([l for l in original_lines if l.strip() and not l.strip().startswith('//')]) + + # Step 1: 移除非法语句 + code = self._transform_forbidden_statements(code) + + # Step 2: 转换层次化访问 + code = self._transform_hierarchical_access(code) + + # Step 3: 智能转换内部信号访问 + code = self._smart_transform_internal_signals(code) + + # Step 4: 最后清理 + code = self._final_cleanup(code) + + # 计算质量分数 + quality_score = self._calculate_quality_score(total_lines) + + # 决定是否需要重试 + should_retry = quality_score < 50 or len(self.violations['critical']) > 3 + + result = { + 'quality_score': quality_score, + 'is_valid': len(self.violations['critical']) == 0, + 'violations': self.violations, + 'transformations': self.transformations, + 'should_retry': should_retry, + 'allowed_signals': self._get_allowed_signals_info() + } + + return code.strip(), result + + def _extract_signals_from_tb(self, tb_code: str): + """从测试平台代码中提取DUT输入输出信号""" + dut_match = re.search(r'(\w+)\s+(?:DUT|dut|uut|UUT)\s*\(', tb_code, re.IGNORECASE) + if dut_match: + start = dut_match.start() + bracket_count = 0 + end = start + for i, char in enumerate(tb_code[start:]): + if char == '(': + bracket_count += 1 + elif char == ')': + bracket_count -= 1 + if bracket_count == 0: + end = start + i + 1 + break + + dut_instance = tb_code[start:end] + port_pattern = r'\.(\w+)\s*\(\s*(\w+)\s*\)' + + for match in re.finditer(port_pattern, dut_instance): + signal_name = match.group(2) + + is_input = re.search(rf'\breg\s+(?:\[[\d:]+\]\s*)?{re.escape(signal_name)}\s*[;,\n]', tb_code) + is_output = re.search(rf'\bwire\s+(?:\[[\d:]+\]\s*)?{re.escape(signal_name)}\s*[;,\n]', tb_code) + + if is_input and signal_name not in self.dut_inputs: + self.dut_inputs.append(signal_name) + if is_output and signal_name not in self.dut_outputs: + self.dut_outputs.append(signal_name) + + # 备用方案:通过reg/wire声明推断 + if not self.dut_inputs and not self.dut_outputs: + for match in re.finditer(r'\breg\s+(?:\[[\d:]+\]\s*)?(\w+)\s*[;,\n]', tb_code): + signal = match.group(1) + if signal.lower() not in ['file', 'scenario', 'i', 'j', 'k', 'cnt']: + if signal not in self.dut_inputs: + self.dut_inputs.append(signal) + + for match in re.finditer(r'\bwire\s+(?:\[[\d:]+\]\s*)?(\w+)\s*[;,\n]', tb_code): + signal = match.group(1) + if signal not in self.dut_outputs: + self.dut_outputs.append(signal) + + def _transform_forbidden_statements(self, code: str) -> str: + """转换非法语句""" + for pattern, desc, severity in self.FORBIDDEN_STATEMENTS: + matches = list(re.finditer(pattern, code, re.IGNORECASE)) + for match in reversed(matches): + signal = match.group(1) if match.groups() else 'unknown' + self.violations[severity].append(f"{desc}: {signal}") + + line_start = code.rfind('\n', 0, match.start()) + 1 + line_end = code.find('\n', match.end()) + if line_end == -1: + line_end = len(code) + original_line = code[line_start:line_end] + + # 尝试转换 force -> 直接赋值(仅对输入信号) + if 'force' in match.group(0).lower() and signal in self.dut_inputs: + new_line = re.sub(r'\bforce\s+', '', original_line, flags=re.IGNORECASE) + code = code[:line_start] + new_line + code[line_end:] + self.transformations.append({ + 'type': 'force_to_assign', + 'original': original_line.strip(), + 'transformed': new_line.strip() + }) + continue + + code = code[:line_start] + '// [BLOCKED] ' + original_line.lstrip() + code[line_end:] + self.transformations.append({ + 'type': 'blocked', + 'original': original_line.strip(), + 'reason': desc + }) + + return code + + def _transform_hierarchical_access(self, code: str) -> str: + """转换层次化访问(如 DUT.state)""" + for match in re.finditer(self.HIERARCHICAL_ACCESS, code): + prefix = match.group(1) + signal = match.group(2) + + if prefix.upper() in ['DUT', 'UUT', 'TOP', 'TB']: + if signal not in self.dut_outputs: + self.violations['critical'].append(f"层次化访问内部信号: {prefix}.{signal}") + + line_start = code.rfind('\n', 0, match.start()) + 1 + line_end = code.find('\n', match.end()) + if line_end == -1: + line_end = len(code) + original_line = code[line_start:line_end] + code = code[:line_start] + '// [HIERARCHY] ' + original_line.lstrip() + code[line_end:] + + return code + + def _smart_transform_internal_signals(self, code: str) -> str: + """智能转换内部信号访问""" + lines = code.split('\n') + transformed_lines = [] + + for line in lines: + stripped = line.strip() + + if stripped.startswith('//') or not stripped: + transformed_lines.append(line) + continue + + if (stripped.startswith('#') or stripped.startswith('$') or + stripped.startswith('repeat(') or stripped.startswith('@(')): + transformed_lines.append(line) + continue + + detected_signals = self._detect_internal_signals_in_line(stripped) + has_critical = detected_signals.get('critical', []) + has_warning = detected_signals.get('warning', []) + + if not has_critical and not has_warning: + transformed_lines.append(line) + continue + + context = self._analyze_signal_context(stripped, detected_signals) + + if context['type'] == 'assignment': + transformed_lines.append(f"// [INTERNAL_ASSIGN] Cannot modify internal signal") + transformed_lines.append(f"// Original: {stripped}") + self.violations['critical'].append(f"尝试修改内部信号: {context['signals']}") + elif context['type'] == 'condition': + transformed = self._transform_condition(stripped, context) + transformed_lines.append(transformed) + self.transformations.append({ + 'type': 'condition_transform', + 'original': stripped, + 'transformed': transformed + }) + elif context['type'] == 'wait_for_state': + transformed = self._transform_state_wait(stripped, context) + transformed_lines.append(transformed) + self.transformations.append({ + 'type': 'wait_transform', + 'original': stripped, + 'transformed': transformed + }) + else: + if has_critical: + transformed_lines.append(f"// [WARNING] Contains internal signal reference: {has_critical}") + transformed_lines.append(f"// Original: {stripped}") + for sig in has_critical: + self.violations['warning'].append(f"可疑的内部信号访问: {sig}") + else: + transformed_lines.append(line) + + return '\n'.join(transformed_lines) + + def _detect_internal_signals_in_line(self, line: str) -> Dict[str, List[str]]: + """检测行中的内部信号""" + detected = {'critical': [], 'warning': [], 'info': []} + + LEGAL_KEYWORDS = { + 'repeat', 'posedge', 'negedge', 'begin', 'end', 'if', 'else', + 'while', 'for', 'case', 'default', 'always', 'initial', + 'assign', 'wire', 'reg', 'input', 'output', 'inout', + 'parameter', 'localparam', 'integer', 'real', 'time', + 'clk', 'clock', 'reset', 'rst', 'areset', 'rst_n', + 'enable', 'ena', 'valid', 'ready', 'data', 'addr', 'address', + 'true', 'false', 'idle', 'wait' + } + + SYSTEM_FUNCTIONS = {'$display', '$write', '$monitor', '$fopen', '$fclose', + '$fdisplay', '$fwrite', '$readmemh', '$readmemb', + '$finish', '$stop', '$random', '$time', '$stime'} + + for severity, patterns in self.INTERNAL_SIGNAL_PATTERNS.items(): + for pattern, name in patterns: + matches = re.findall(pattern, line, re.IGNORECASE) + if matches: + for match in matches: + if isinstance(match, tuple): + match = match[0] if match[0] else match[1] + + match_lower = match.lower() if match else '' + + if match_lower in LEGAL_KEYWORDS: + continue + if match in SYSTEM_FUNCTIONS: + continue + if match in self.dut_inputs or match in self.dut_outputs: + continue + if match.startswith('$'): + continue + + if match and match not in detected[severity]: + detected[severity].append(match) + + return detected + + def _analyze_signal_context(self, line: str, signals: Dict) -> Dict: + """分析信号使用上下文""" + assign_match = re.search(r'(\w+)\s*(?:=|<=)\s*', line) + if assign_match: + target = assign_match.group(1) + if target in signals.get('critical', []) or target in signals.get('warning', []): + return {'type': 'assignment', 'signals': [target], 'line': line} + + if re.search(r'wait\s*\([^)]*state', line, re.IGNORECASE): + return {'type': 'wait_for_state', 'signals': signals.get('critical', []), 'line': line} + + if re.search(r'if\s*\(|while\s*\(|@\s*\(', line): + return {'type': 'condition', 'signals': signals.get('critical', []) + signals.get('warning', []), 'line': line} + + return {'type': 'other', 'signals': signals.get('critical', []) + signals.get('warning', []), 'line': line} + + def _transform_condition(self, line: str, context: Dict) -> str: + """转换条件判断语句""" + original = line + + if 'state' in str(context['signals']): + indent = len(line) - len(line.lstrip()) + spaces = ' ' * indent + + transformed = f"// [TRANSFORMED] Original: {original.strip()}\n" + transformed += f"{spaces}// Cannot directly check internal state\n" + transformed += f"{spaces}// Alternative: Wait for expected clock cycles\n" + transformed += f"{spaces}repeat(5) @(posedge clk); // Adjust cycles as needed" + + self.violations['warning'].append(f"条件判断转换: {original.strip()}") + return transformed + + return f"// [TRANSFORMED] {original}" + + def _transform_state_wait(self, line: str, context: Dict) -> str: + """转换状态等待语句""" + indent = len(line) - len(line.lstrip()) + spaces = ' ' * indent + + transformed = f"// [TRANSFORMED] Original: {line.strip()}\n" + transformed += f"{spaces}// Cannot wait for internal state directly\n" + transformed += f"{spaces}// Alternative: Drive inputs and wait for expected cycles\n" + transformed += f"{spaces}repeat(10) @(posedge clk); // Adjust based on FSM design" + + self.violations['info'].append(f"状态等待转换: {line.strip()}") + return transformed + + def _final_cleanup(self, code: str) -> str: + """最终清理""" + lines = code.split('\n') + cleaned = [] + + for line in lines: + stripped = line.strip() + + if stripped in ['begin', 'end'] and cleaned: + last = cleaned[-1].strip() + if last.startswith('// [TRANSFORMED]') or last.startswith('// [INTERNAL'): + continue + + cleaned.append(line) + + result = '\n'.join(cleaned) + result = re.sub(r'\n\s*\n\s*\n', '\n\n', result) + + return result + + def _calculate_quality_score(self, total_lines: int) -> int: + """计算代码质量分数""" + if total_lines == 0: + return 0 + + score = 100 + score -= len(self.violations['critical']) * 20 + score -= len(self.violations['warning']) * 5 + score += len([t for t in self.transformations if 'blocked' not in t.get('type', '')]) * 5 + + return max(0, min(100, score)) + + def _get_allowed_signals_info(self) -> Dict: + """获取允许的信号信息""" + return { + 'inputs': self.dut_inputs, + 'outputs': self.dut_outputs, + 'all_allowed': self.dut_inputs + self.dut_outputs + } + + def generate_constraint_prompt(self) -> str: + """ + 生成动态约束提示 + + 如果从 DUT 提取了信号名,则使用精确的信号列表 + 否则使用通用的约束提示 + """ + # [优先] 如果已经从 DUT 提取了信号名,使用精确的约束 + if self.dut_all_signals: + return self.generate_signal_constraint_prompt() + + # [备选] 使用通用约束提示 + reset_signal = self._find_reset_signal() + inputs_list = str(self.dut_inputs) if self.dut_inputs else "[]" + + # 动态获取禁止信号名示例 + forbidden_examples = self.get_forbidden_examples(count=8) + forbidden_str = ", ".join(f'"{ex}"' for ex in forbidden_examples[:4]) + forbidden_str2 = ", ".join(f'"{ex}"' for ex in forbidden_examples[4:8]) if len(forbidden_examples) > 4 else "" + + prompt = f""" +{'='*60} +📋 [SIGNAL CONSTRAINTS - DERIVED FROM DUT] +{'='*60} +""" + + if self.dut_inputs: + prompt += f""" +✅ ALLOWED INPUT SIGNALS (you CAN drive these): + {inputs_list} + +🚫 FORBIDDEN SIGNAL NAMES - DO NOT USE THESE: + Generic patterns: {forbidden_str} +""" + if forbidden_str2: + prompt += f" More examples: {forbidden_str2}\n" + + prompt += f""" + ⚠️ Also forbidden: "reset" (actual: "{reset_signal}"), "rst", "enable", "en" + ⚠️ Any name NOT in ALLOWED INPUTS above is FORBIDDEN! + +⚠️ CRITICAL RULE: You MUST use ONLY the signal names from ALLOWED INPUTS! + If a signal name is not in the list, it does NOT exist in this design! +""" + else: + prompt += """ +⚠️ WARNING: Could not extract input signals from testbench. +Please check the testbench code for actual signal names before writing test code. +""" + + if self.dut_outputs: + outputs_list = str(self.dut_outputs) + prompt += f""" +📖 OUTPUT SIGNALS (you can READ but NOT write): + {outputs_list} +""" + + prompt += f""" +🚫 FORBIDDEN ACTIONS: + 1. NEVER assign to internal signals (state, counter, etc.) + 2. NEVER use 'force' or 'assign' statements + 3. NEVER access DUT.state (hierarchical access) + 4. NEVER guess signal names - use ONLY from ALLOWED INPUTS! + +✅ CORRECT APPROACH: + - To reach FSM state: drive inputs and WAIT for transition + - Example: {reset_signal} = 1; repeat(2) @(posedge clk); {reset_signal} = 0; +""" + return prompt + + def _find_reset_signal(self) -> str: + """查找复位信号名""" + # 按优先级查找常见的复位信号名 + reset_candidates = ['areset', 'rst_n', 'rst', 'reset', 'rst_b'] + for sig in reset_candidates: + if sig in self.dut_inputs: + return sig + # 如果没找到,检查输入列表中是否有类似名称 + for sig in self.dut_inputs: + sig_lower = sig.lower() + if 'reset' in sig_lower or 'rst' in sig_lower: + return sig + # 默认返回第一个输入信号(排除 clk) + for sig in self.dut_inputs: + if 'clk' not in sig.lower(): + return sig + return "reset" # 兜底 + + # ========================================================================= + # [新增] 通用禁止信号名检测 + # ========================================================================= + def is_forbidden_signal_name(self, name: str) -> Tuple[bool, str]: + """ + 检测信号名是否为禁止的"猜测型"名称 + + Args: + name: 待检测的信号名 + + Returns: + (is_forbidden, reason): 是否禁止及原因 + """ + name_lower = name.lower() + + # 如果信号名在允许列表中,则不禁止 + if name in self.dut_inputs or name in self.dut_outputs: + return False, "" + + # 检查是否匹配禁止模式 + for pattern, reason in self.FORBIDDEN_NAME_PATTERNS: + if re.match(pattern, name_lower, re.IGNORECASE): + return True, reason + + return False, "" + + def get_forbidden_examples(self, count: int = 6) -> List[str]: + """ + 动态生成禁止信号名示例 + + 基于实际 DUT 信号生成有针对性的错误示例 + + Args: + count: 返回的示例数量 + + Returns: + 禁止信号名示例列表 + """ + examples = [] + + # 1. 带数字后缀的通用名(始终禁止) + generic_patterns = [ + 'input_signal_1', 'input_signal_2', + 'in_1', 'in_2', 'in_3', + 'data_1', 'data_2', + 'signal_1', 'signal_2', + ] + examples.extend(generic_patterns[:count]) + + # 2. 基于实际信号生成"错误猜测"示例 + reset_signal = self._find_reset_signal() + + # 如果实际复位信号不是 reset/rst,则添加这些为禁止示例 + if reset_signal and reset_signal not in ['reset', 'rst']: + if reset_signal.lower() != 'reset': + examples.append('reset') # 错误:应该用 areset + if reset_signal.lower() != 'rst': + examples.append('rst') # 错误:应该用 areset + + # 3. 检查是否有时钟信号 + clk_signals = [s for s in self.dut_inputs if 'clk' in s.lower()] + if clk_signals: + actual_clk = clk_signals[0] + if actual_clk != 'clk': + examples.append('clk') # 错误:应该用实际时钟名 + + # 4. 检查是否有使能信号 + en_signals = [s for s in self.dut_inputs if 'en' in s.lower() or 'enable' in s.lower()] + if not en_signals: + examples.append('enable') # 不存在的使能信号 + examples.append('en') + + # 去重并限制数量 + seen = set() + unique_examples = [] + for ex in examples: + if ex not in seen and ex not in self.dut_inputs: + seen.add(ex) + unique_examples.append(ex) + if len(unique_examples) >= count: + break + + return unique_examples + + def detect_forbidden_signals_in_code(self, code: str) -> List[Dict]: + """ + 检测代码中使用的禁止信号名 + + Args: + code: Verilog 代码 + + Returns: + 检测到的禁止信号列表 + """ + forbidden_signals = [] + + # 提取代码中使用的所有信号名(赋值语句左侧) + # 匹配: signal = value 形式 + for match in re.finditer(r'^\s*(\w+)\s*=', code, re.MULTILINE): + signal = match.group(1) + is_forbidden, reason = self.is_forbidden_signal_name(signal) + if is_forbidden: + forbidden_signals.append({ + 'signal': signal, + 'reason': reason, + 'line': match.group(0).strip() + }) + + return forbidden_signals + + # ========================================================================= + # [新增] Verilog 语法预检查 - 检测常见逻辑错误 + # ========================================================================= + def check_syntax_issues(self, code: str, signal_widths: Dict[str, int] = None, declared_signals: set = None) -> Dict: + """ + 检测 Verilog 代码中的常见语法/逻辑问题 + + Args: + code: 待检查的代码 + signal_widths: 信号位宽映射 (可选,如 {'in': 1, 'data': 8}) + declared_signals: 已声明的信号集合 (可选,用于检测未声明信号) + + Returns: + { + 'width_mismatch': [...], # 位宽不匹配警告 + 'logic_issues': [...], # 逻辑问题 + 'syntax_warnings': [...], # 语法警告 + 'should_retry': bool # 是否建议重试 + } + """ + result = { + 'width_mismatch': [], + 'logic_issues': [], + 'syntax_warnings': [], + 'should_retry': False + } + + # 检查位宽不匹配 + result['width_mismatch'] = self._check_width_mismatch(code, signal_widths) + + # 检查逻辑问题 + result['logic_issues'] = self._check_logic_issues(code, signal_widths) + + # 检查其他语法问题(传入已声明信号) + result['syntax_warnings'] = self._check_syntax_warnings(code, declared_signals) + + # 决定是否需要重试 + # 包括:位宽不匹配、逻辑问题、语法错误(severity='error') + has_syntax_errors = any( + issue.get('severity') == 'error' + for issue in result['syntax_warnings'] + ) + result['should_retry'] = ( + len(result['width_mismatch']) > 0 or + len(result['logic_issues']) > 0 or + has_syntax_errors + ) + + return result + + def _check_width_mismatch(self, code: str, signal_widths: Dict[str, int] = None) -> List[Dict]: + """ + 检测位宽不匹配问题 + + 常见问题: + - {signal} = N'b... 将多位值赋给单比特信号 + - signal = N'b... 位宽不匹配 + """ + issues = [] + signal_widths = signal_widths or {} + + # 默认假设未声明信号为 1 位 + def get_width(sig): + return signal_widths.get(sig, 1) + + # 模式1: {signal} = N'bvalue (拼接赋值) + # 例: {in} = 8'b01111100 - 将 8 位赋给 1 位 + concat_pattern = re.compile(r'\{(\w+)\}\s*=\s*(\d+)\'([bhd])([0-9a-fA-FxXzZ_]+)') + for match in concat_pattern.finditer(code): + signal = match.group(1) + value_width = int(match.group(2)) + base = match.group(3) + value = match.group(4) + + actual_width = get_width(signal) + + if value_width > actual_width: + issues.append({ + 'type': 'concat_width_mismatch', + 'signal': signal, + 'signal_width': actual_width, + 'assigned_width': value_width, + 'original': match.group(0), + 'message': f"Signal '{signal}' is {actual_width}-bit, but assigned {value_width}-bit value via concatenation. Verilog will truncate.", + 'severity': 'warning', + 'suggestion': f"Use a shift register: reg [{value_width-1}:0] temp; temp = {value_width}'{base}{value}; then shift bits one by one" + }) + + # 模式2: signal = N'bvalue (直接赋值) + assign_pattern = re.compile(r'\b(\w+)\s*=\s*(\d+)\'([bhd])([0-9a-fA-FxXzZ_]+)') + for match in assign_pattern.finditer(code): + signal = match.group(1) + value_width = int(match.group(2)) + + # 跳过拼接赋值(已处理) + if f'{{{signal}}}' in match.group(0): + continue + + actual_width = get_width(signal) + + # 只有当信号已知且位宽不匹配时才警告 + if signal in signal_widths and value_width > actual_width: + issues.append({ + 'type': 'direct_width_mismatch', + 'signal': signal, + 'signal_width': actual_width, + 'assigned_width': value_width, + 'original': match.group(0), + 'message': f"Signal '{signal}' is {actual_width}-bit, but assigned {value_width}-bit value. Truncation will occur.", + 'severity': 'warning' + }) + + return issues + + def _check_logic_issues(self, code: str, signal_widths: Dict[str, int] = None) -> List[Dict]: + """ + 检测逻辑问题 + + 常见问题: + - 单比特信号自移位 (in = in >> 1 无效果) + - 无效的循环条件 + """ + issues = [] + signal_widths = signal_widths or {} + + def get_width(sig): + return signal_widths.get(sig, 1) + + # 模式: signal = signal >> N 或 signal = signal << N + shift_pattern = re.compile(r'\b(\w+)\s*=\s*\1\s*(>>|<<)\s*(\d+)?') + for match in shift_pattern.finditer(code): + signal = match.group(1) + direction = match.group(2) + shift_amount = int(match.group(3)) if match.group(3) else 1 + + actual_width = get_width(signal) + + if actual_width == 1: + issues.append({ + 'type': 'single_bit_shift', + 'signal': signal, + 'direction': direction, + 'original': match.group(0), + 'message': f"Single-bit signal '{signal}' self-shift has no effect. Result is always 0.", + 'severity': 'warning', + 'suggestion': f"Use a shift register for bit-serial input, not the input signal itself" + }) + + # 模式: repeat(N) begin ... signal = signal >> 1; end (循环移位单比特) + repeat_shift_pattern = re.compile(r'repeat\s*\(\s*\d+\s*\)\s*begin[^}]*?(\w+)\s*=\s*\1\s*(>>|<<)', re.DOTALL) + for match in repeat_shift_pattern.finditer(code): + signal = match.group(1) + actual_width = get_width(signal) + + if actual_width == 1: + issues.append({ + 'type': 'repeat_single_bit_shift', + 'signal': signal, + 'original': match.group(0)[:100] + '...', + 'message': f"Repeat loop shifting single-bit signal '{signal}' is ineffective", + 'severity': 'warning' + }) + + return issues + + def _check_syntax_warnings(self, code: str, declared_signals: set = None) -> List[Dict]: + """ + 检测其他语法问题 + + Args: + code: 待检查的代码 + declared_signals: 已声明的信号集合 (从完整 TB 中提取) + """ + issues = [] + declared_signals = declared_signals or set() + + # 检查: 缺少分号 + # 注意: 这只是简单检查,不是完整解析 + lines = code.split('\n') + for i, line in enumerate(lines): + stripped = line.strip() + if not stripped or stripped.startswith('//'): + continue + + # 跳过不需要分号的行 + skip_patterns = [ + r'^begin$', r'^end$', r'^endcase$', r'^endmodule$', + r'^else$', r'^\)$', r'^\}\s*$', r'^`timescale', r'^`include' + ] + if any(re.match(p, stripped) for p in skip_patterns): + continue + + # 检查是否需要分号但没有 + needs_semicolon = re.search(r'\b(initial|always|assign|reg|wire|parameter|localport)\b', stripped) is None + has_semicolon = stripped.endswith(';') or stripped.endswith(')') or stripped.endswith('}') + + if needs_semicolon and not has_semicolon and not stripped.endswith('begin'): + # 可能缺少分号(但不确定) + pass # 暂不报警,避免误报 + + # 检查: 不匹配的 begin/end + begin_count = len(re.findall(r'\bbegin\b', code)) + end_count = len(re.findall(r'\bend\b', code)) + if begin_count != end_count: + issues.append({ + 'type': 'mismatched_begin_end', + 'message': f"Mismatched begin/end: {begin_count} begin vs {end_count} end", + 'severity': 'error' + }) + + # 检查: 未声明的信号(在赋值左侧使用的信号) + # 使用传入的已声明信号集合 + for match in re.finditer(r'^\s*(\w+)\s*=', code, re.MULTILINE): + signal = match.group(1) + # 跳过系统任务和关键字 + if signal in ['if', 'else', 'case', 'for', 'while', 'repeat', 'assign', 'force', 'release']: + continue + # 跳过以 $ 开头的系统任务 + if signal.startswith('$'): + continue + # 检查是否在已声明信号列表中 + if signal not in declared_signals: + issues.append({ + 'type': 'undeclared_signal', + 'signal': signal, + 'message': f"Signal '{signal}' is used but not declared in the testbench", + 'severity': 'error', + 'suggestion': f"Use an existing signal name (declared: {', '.join(list(declared_signals)[:10])}...)" + }) + + # 检查: always 块与时钟生成冲突 + # 检测是否有多个 always/initial 块驱动同一信号 + always_blocks = re.findall(r'\balways\s*(@[^i]|begin)', code) + initial_clk_blocks = len(re.findall(r'initial\s+begin[^i]*?clk\s*=', code, re.DOTALL)) + always_clk_blocks = len(re.findall(r'\balways[^i]*?clk\s*=', code, re.DOTALL)) + + if initial_clk_blocks > 0 and always_clk_blocks > 0: + issues.append({ + 'type': 'multiple_clock_drivers', + 'message': f"Multiple clock drivers detected: {initial_clk_blocks} initial + {always_clk_blocks} always blocks driving clk", + 'severity': 'error', + 'suggestion': "Remove duplicate clock generation. The testbench already has clock generation." + }) + + # 检查: initial 块嵌套(生成了 initial begin ... end 在注入时会导致嵌套) + if re.search(r'\binitial\s+begin\b', code): + issues.append({ + 'type': 'initial_block_injection', + 'message': "Code contains 'initial begin...end' block which should not be injected into an existing initial block", + 'severity': 'error', + 'suggestion': "Remove the 'initial begin...end' wrapper, keep only the test statements inside" + }) + + return issues + + +# ============================================================================ +# CoverageParser - 覆盖率解析器 +# ============================================================================ +class CoverageParser: + """覆盖率解析器 - 从带注释的Verilog文件中提取未覆盖的代码块 + + [增强] 集成语义分析结果,提供更精准的 FSM 状态路径指导 + [新增] 集成能量分配层,提供目标功能点优先级信息 + [新增] 集成多样性约束注入器,避免测试用例同质化 + [新增] 从 DUT 代码提取信号名,精确约束 LLM + """ + + def __init__(self, annotated_file, tb_code=None, semantic_result=None, + energy_allocator=None, diversity_injector=None, dut_code=None): + self.file_path = annotated_file + self.tb_code = tb_code + self.semantic_result = semantic_result # [新增] 语义分析结果 + self.energy_allocator = energy_allocator # [新增] 能量分配器 + self.diversity_injector = diversity_injector # [新增] 多样性约束注入器 + self.dut_code = dut_code # [新增] DUT 代码 + # 修复:Verilator 覆盖率标记格式多样化: + # %NNNNNN - 行覆盖计数(%000000 表示从未执行) + # ~NNNNNN - 分支/条件覆盖计数(~000000 表示分支从未执行) + # ^NNNNNN - 未覆盖的分支标记 + # NNNNNN - 空格开头+数字(某些 Verilator 版本) + # NNNNNN - 纯数字开头(无前缀) + self.line_pattern = re.compile(r'^%(\d+)\s+(.*)$') # 匹配 %NNNNNN code + self.tilde_pattern = re.compile(r'^~(\d+)\s+(.*)$') # 匹配 ~NNNNNN code + self.caret_pattern = re.compile(r'^\^(\d+)\s+(.*)$') # 匹配 ^NNNNNN code + # [修复] 纯数字开头(无前缀)或空格开头 + self.plain_pattern = re.compile(r'^\s*(\d+)\s+(.*)$') # 匹配 " NNNNNN" 或 "NNNNNN" + self.decl_pattern = re.compile(r'^\s*(input|output|inout|wire|reg|logic|parameter|localparam|assign)\b') + + # [修改] 传递 DUT 代码给 BlackBoxValidator + self.validator = BlackBoxValidator(dut_code=dut_code) + if tb_code: + self.validator._extract_signals_from_tb(tb_code) + + def generate_prompt(self, current_score): + """生成覆盖率驱动的Prompt""" + if not os.path.exists(self.file_path): + return None + + try: + with open(self.file_path, 'r', encoding='utf-8', errors='ignore') as f: + lines = f.readlines() + except Exception: + return None + + missing_blocks = [] + current_block = [] + recording = False + context_buffer = [] + CONTEXT_SIZE = 3 + + # 收集缺失行用于 FSM 分析 + missing_lines = [] + + for i, line in enumerate(lines): + line = line.strip() + count = -1 + clean_code = line + is_tilde = False + is_caret = False + + # 尝试匹配各种覆盖率标记格式 + # Verilator 覆盖率格式: + # - %NNNNNN: 行覆盖,NNNNNN 是执行次数,%000000 表示未执行 + # - ~NNNNNN: 分支/条件覆盖,~000000 表示分支从未执行 + # - ^NNNNNN: 未覆盖分支标记 + # - NNNNNN: 无前缀格式(某些版本) + match_pct = self.line_pattern.match(line) # %NNNNNN code + match_tilde = self.tilde_pattern.match(line) # ~NNNNNN code + match_caret = self.caret_pattern.match(line) # ^NNNNNN code + match_plain = self.plain_pattern.match(line) # NNNNNN code (无前缀) + + if match_pct: + count = int(match_pct.group(1)) + clean_code = match_pct.group(2).strip() + elif match_tilde: + count = int(match_tilde.group(1)) + clean_code = match_tilde.group(2).strip() + is_tilde = True + elif match_caret: + count = int(match_caret.group(1)) + clean_code = match_caret.group(2).strip() + is_caret = True + elif match_plain: + # 纯数字格式(可能出现在某些 Verilator 版本) + count = int(match_plain.group(1)) + clean_code = match_plain.group(2).strip() + + if "//" in clean_code: + clean_code = clean_code.split("//")[0].strip() + + is_hard_noise = (self.decl_pattern.match(clean_code) or clean_code == "endmodule") + is_soft_noise = (len(clean_code) < 2 or clean_code in ["end", "begin", "else", ");", "endcase", "default:"] or + clean_code.startswith("module ") or not any(c.isalnum() for c in clean_code)) + + # [修改] 覆盖状态判断: + # - %NNNNNN: count > 0 表示已覆盖,count == 0 表示未覆盖 + # - ~NNNNNN: 分支覆盖标记,count == 0 也表示未覆盖! + # - ^NNNNNN: 未覆盖分支标记 + is_definitely_covered = (not is_tilde and not is_caret and count > 0) + # [关键修复] tilde 格式 count == 0 也应该被视为 missing + is_definitely_missed = ( + (not is_tilde and not is_caret and count == 0 and not is_hard_noise and not is_soft_noise) or + (is_tilde and count == 0 and not is_hard_noise and not is_soft_noise) or # [新增] ~000000 也是 missing + (is_caret and not is_hard_noise and not is_soft_noise) + ) + + if recording: + if is_definitely_covered: + missing_blocks.append(current_block) + missing_lines.extend(current_block) + current_block = [] + recording = False + if not is_hard_noise: + context_buffer.append(clean_code) + else: + if not is_hard_noise and not (is_soft_noise and len(clean_code) < 4): + current_block.append(f"Line {i+1}: {clean_code}") + else: + if is_definitely_missed: + recording = True + if context_buffer: + current_block.append(f"... (Context)") + for ctx in context_buffer: + current_block.append(f" {ctx}") + current_block.append(f"Line {i+1}: {clean_code} <--- MISSING START") + else: + if not is_hard_noise and not (is_soft_noise and len(clean_code) < 4): + context_buffer.append(clean_code) + if len(context_buffer) > CONTEXT_SIZE: + context_buffer.pop(0) + + if recording and current_block: + missing_blocks.append(current_block) + missing_lines.extend(current_block) + + # [改进] 详细诊断日志 - 使用 info 级别确保可见 + total_lines = len(lines) + parsed_lines = sum(1 for l in lines if l.strip() and ( + self.line_pattern.match(l.strip()) or + self.tilde_pattern.match(l.strip()) or + self.caret_pattern.match(l.strip()) or + self.plain_pattern.match(l.strip()) + )) + + # 收集零计数行的详细信息 + zero_count_details = [] + for l in lines: + l_stripped = l.strip() + if not l_stripped: + continue + match_pct = self.line_pattern.match(l_stripped) + match_tilde = self.tilde_pattern.match(l_stripped) + if match_pct and int(match_pct.group(1)) == 0: + zero_count_details.append(('%', match_pct.group(2).strip()[:50])) + elif match_tilde and int(match_tilde.group(1)) == 0: + zero_count_details.append(('~', match_tilde.group(2).strip()[:50])) + + zero_count_lines = len(zero_count_details) + + logger.info(f"CoverageParser: Total={total_lines}, Parsed={parsed_lines}, Zero-count={zero_count_lines}, Missing blocks={len(missing_blocks)}") + + if not missing_blocks: + # [改进] 详细诊断信息 + if zero_count_lines > 0: + logger.warning(f"Found {zero_count_lines} lines with zero coverage count, but no missing blocks extracted.") + logger.warning("Zero-count lines:") + for prefix, code in zero_count_details[:10]: # 只显示前10个 + logger.warning(f" {prefix}000000: {code}") + if len(zero_count_details) > 10: + logger.warning(f" ... and {len(zero_count_details) - 10} more") + logger.warning("These lines may have been filtered as noise (declarations, etc.)") + return None + + selected_blocks = missing_blocks[:50] + + # 获取实际信号名用于示例 + reset_signal = self.validator._find_reset_signal() + inputs_no_clk = [s for s in self.validator.dut_inputs if 'clk' not in s.lower()] + example_signal = inputs_no_clk[0] if inputs_no_clk else (reset_signal if reset_signal != "reset" else "ena") + + # [新增] 动态获取禁止信号名示例 + forbidden_examples = self.validator.get_forbidden_examples(count=8) + forbidden_display = ", ".join(f'"{ex}"' for ex in forbidden_examples[:6]) + + # 分析 FSM 相关的缺失代码 + fsm_analysis = self._analyze_fsm_missing(missing_lines) + + # [新增] 从语义分析结果获取 FSM 和功能点信息 + semantic_context = self._generate_semantic_context() + + # === [新增] 在 prompt 开头添加醒目的信号名清单 === + inputs_json = str(self.validator.dut_inputs) if self.validator.dut_inputs else "[]" + outputs_json = str(self.validator.dut_outputs) if self.validator.dut_outputs else "[]" + + prompt = f""" +[ROLE] +You are a hardware verification expert. Your task is to write a test scenario to improve code coverage. + +{'='*60} +⚠️ [SIGNAL NAME CONSTRAINTS - READ THIS FIRST] ⚠️ +{'='*60} + +📋 ALLOWED INPUT SIGNALS (you CAN drive these): + {inputs_json} + +📋 OUTPUT SIGNALS (you can READ but NOT write): + {outputs_json} + +🚫 FORBIDDEN SIGNAL NAMES - THESE DO NOT EXIST: + {forbidden_display} + ⚠️ Also: "reset" (actual: "{reset_signal}"), "rst", "enable", "en" + ⚠️ ANY name NOT in ALLOWED INPUTS above is FORBIDDEN! + +{'='*60} + +[COVERAGE STATUS] +Current testbench achieves {current_score:.2f}% coverage. +The following logic blocks in the DUT are NEVER executed during simulation: + +""" + for idx, block in enumerate(selected_blocks): + prompt += f"--- Missing Logic Block {idx+1} ---\n" + "\n".join(block) + "\n\n" + + # [新增] 添加语义分析上下文 + if semantic_context: + prompt += f""" +[SEMANTIC ANALYSIS - MODULE UNDERSTANDING] +{semantic_context} +""" + + # === [新增] 添加能量分配目标上下文 === + if self.energy_allocator: + energy_context = self.energy_allocator.get_target_context() + if energy_context: + prompt += f""" +[ENERGY-ALIGNED TARGET - PRIORITY] +{energy_context} +Focus your test scenario on covering this high-priority target first. +""" + # ===================================== + + prompt += self.validator.generate_constraint_prompt() + + # 添加 FSM 分析提示 + if fsm_analysis: + prompt += f""" +[FSM STATE TRANSITION ANALYSIS - CRITICAL] +{fsm_analysis} + +IMPORTANT: FSM transitions have PRIORITY ORDER! +- 'if' conditions are evaluated TOP to BOTTOM +- The FIRST matching condition determines the next state +- To trigger a branch like "else if (condition)", you MUST ensure all higher-priority conditions are FALSE +- Read the missing code's context carefully: what conditions precede it? + +""" + + prompt += f""" +[OUTPUT REQUIREMENTS - CRITICAL] +1. Return ONLY Verilog test scenario code (NOT a task definition) +2. Your code will be inserted INTO an existing `initial begin ... end` block +3. DO NOT wrap your code in `task ... endtask` - just write the test sequence directly +4. DO NOT use `$finish` or `$stop` - the testbench handles simulation end + +[CODING STYLE] +1. Use blocking assignments for input signals: `signal = value;` +2. Use `#N;` for time delays: `#10;` means wait 10 time units +3. Use `repeat(N) @(posedge clk);` to wait for N clock cycles +4. Start with reset sequence if needed + +[BLACK-BOX CONSTRAINTS - CRITICAL] +1. You can ONLY control module INPUTS listed above +2. You CANNOT access internal signals (state, next_state, counters, etc.) +3. You CANNOT use `force` or `assign` on internal signals +4. To trigger a specific state: drive inputs and wait for the FSM to reach it naturally + +[STEP-BY-STEP APPROACH - REQUIRED] +For each missing branch, think through: +1. What STATE must the FSM be in? (Look at the case statement) +2. What CONDITIONS must be true/false? (Check priority order!) +3. How to reach that state from reset? (Trace state transitions) +4. What inputs to apply and in what order? + +[POSITIVE EXAMPLE - CORRECT APPROACH] +```verilog +// Reset sequence - use ACTUAL input signal names from above +{reset_signal} = 1; +repeat(2) @(posedge clk); +{reset_signal} = 0; + +// Wait for FSM to reach desired state (estimate cycles) +repeat(3) @(posedge clk); + +// Trigger missing branch by driving inputs +{example_signal} = 1; +repeat(5) @(posedge clk); +{example_signal} = 0; +repeat(10) @(posedge clk); +``` + +[NEGATIVE EXAMPLE - DO NOT DO THIS] +```verilog +// WRONG: Using wrong signal name (e.g., 'reset' instead of '{reset_signal}') +reset = 1; // ERROR: Signal 'reset' does not exist! Use '{reset_signal}' instead! + +// WRONG: Not considering condition priority in FSM +// If missing code is "else if (condition_b)", you must make condition_a FALSE first! +// Example: if FSM has "if (!signal_a) ... else if (signal_b) ..." +// Then signal_a must be 1 (FALSE) for the else-if branch to execute +signal_a = 0; // WRONG: This blocks the else-if branch! +signal_b = 1; // This will NOT trigger because signal_a=0 took priority + +// CORRECT: Analyze priority, set higher-priority conditions to FALSE +signal_a = 1; // Now the first condition (!signal_a) is FALSE +signal_b = 1; // Now this else-if branch can execute + +// WRONG: Trying to assign internal state +state = IDLE; // ERROR: Cannot modify internal signal! + +// WRONG: Using force on internal signal +force DUT.state = WL; // ERROR: Cannot force internal signal! + +// WRONG: Checking internal state in condition +if (state == WL) begin // ERROR: Cannot read internal signal! + {example_signal} = 1; +end + +// CORRECT ALTERNATIVE: Estimate timing instead +repeat(5) @(posedge clk); // Wait for FSM to reach expected state +{example_signal} = 1; +``` + +{'='*60} +⚠️ [FINAL SIGNAL NAME CHECK - BEFORE YOU WRITE CODE] ⚠️ +{'='*60} + +🚫 STOP! Verify your signal names: + +✅ ALLOWED INPUTS: {inputs_json} +🚫 FORBIDDEN: {forbidden_display} + +❌ WRONG: input_signal_1 = 1; // Does NOT exist! +❌ WRONG: input_signal_2 = 0; // Does NOT exist! +❌ WRONG: reset = 1; // Wrong! Use '{reset_signal}' instead! +❌ WRONG: data_1 = 1; // Does NOT exist! + +✅ CORRECT: {example_signal} = 1; // From ALLOWED INPUTS +✅ CORRECT: {reset_signal} = 1; // Actual reset signal + +⚠️ RULE: If a signal name is NOT in ALLOWED INPUTS, it does NOT exist! +Now write the test scenario code using ONLY signal names from ALLOWED INPUTS. +""" + + # === [新增] 注入多样性约束 === + if self.diversity_injector: + # 获取未覆盖功能点 + uncovered_functions = [] + if self.semantic_result and self.semantic_result.get('function_points'): + uncovered_functions = [ + fp for fp in self.semantic_result['function_points'] + if not fp.get('covered', False) + ] + + # 获取当前目标功能点 + target_function = "" + if self.energy_allocator and self.energy_allocator.current_target: + target_function = self.energy_allocator.current_target.function_point + + # 注入多样性约束 + prompt = self.diversity_injector.inject_diversity_constraints( + prompt=prompt, + target_function=target_function, + uncovered_functions=uncovered_functions + ) + # ================================= + + return prompt + + def _analyze_fsm_missing(self, missing_lines: List[str]) -> str: + """分析 FSM 相关的缺失代码,生成具体的 FSM 状态转换指导""" + analysis = [] + + # 检查是否涉及 FSM 状态转换 + has_state_case = any('case' in line.lower() and 'state' in line.lower() for line in missing_lines) + has_else_if = any('else if' in line.lower() for line in missing_lines) + has_if_condition = any(re.search(r'\bif\s*\(', line) for line in missing_lines) + + if has_state_case or has_else_if: + analysis.append("- Missing code involves FSM state transitions or conditional branches") + + if has_else_if or has_if_condition: + analysis.append("- Conditional branches have PRIORITY ORDER (top to bottom)") + analysis.append("- 'else if' branches require ALL previous conditions to be FALSE") + analysis.append("- Analyze the missing code's context: what conditions block this branch?") + + if has_state_case: + analysis.append("- To trigger a state transition: first reach the source state, then drive inputs") + + # === 新增:FSM 状态路径分析 === + # 尝试从缺失代码中提取 FSM 状态信息 + fsm_state_info = self._extract_fsm_state_from_missing(missing_lines) + if fsm_state_info: + analysis.append("") + analysis.append("[FSM STATE PATH ANALYSIS]") + analysis.extend(fsm_state_info) + + return "\n".join(analysis) if analysis else "" + + def _extract_fsm_state_from_missing(self, missing_lines: List[str]) -> List[str]: + """ + 从缺失代码中提取 FSM 状态信息,生成具体的状态转换指导 + + 分析策略: + 1. 从缺失代码的上下文识别 case 分支(FSM 状态) + 2. 分析该状态下的条件分支优先级 + 3. 识别需要满足的输入条件 + """ + info = [] + + # 从 annotated 文件中读取完整的 DUT 代码以分析 FSM 结构 + try: + with open(self.file_path, 'r', encoding='utf-8', errors='ignore') as f: + full_content = f.read() + except: + return info + + # 提取缺失代码所在的 FSM 状态 + target_state = None + missing_condition = None + + for line in missing_lines: + # 查找 case 分支标记(如 "WL:", "WR:", "FALLL:" 等) + # 格式可能是 "Line N: STATE:" 或 "STATE:" + state_match = re.search(r'\b([A-Z][A-Z0-9_]*)\s*:', line) + if state_match: + potential_state = state_match.group(1) + # 排除常见的非状态关键字 + if potential_state not in ['IF', 'ELSE', 'CASE', 'BEGIN', 'END', 'DEFAULT']: + target_state = potential_state + break + + # 如果没找到,尝试从整个文件中分析 + if not target_state: + # 查找缺失行附近的 case 分支 + lines = full_content.split('\n') + for i, line in enumerate(lines): + # 查找覆盖率标记为 0 的行 + if re.match(r'^%000000', line.strip()): + # 向上查找最近的 case 分支(状态) + for j in range(i-1, max(0, i-20), -1): + state_match = re.search(r'^\s*([A-Z][A-Z0-9_]*)\s*:', lines[j]) + if state_match: + target_state = state_match.group(1) + break + if target_state: + break + + # 分析缺失的条件分支 + for line in missing_lines: + # 提取 else if 条件 + else_if_match = re.search(r'else\s+if\s*\(([^)]+)\)', line) + if else_if_match: + missing_condition = else_if_match.group(1) + break + # 提取 if 条件 + if_match = re.search(r'\bif\s*\(([^)]+)\)', line) + if if_match: + missing_condition = if_match.group(1) + break + + # 生成具体的指导信息 + if target_state: + info.append(f"- Target FSM state identified: {target_state}") + + # 查找复位后的初始状态 + reset_state = self._find_reset_state(full_content) + if reset_state: + info.append(f"- After reset, FSM starts in state: {reset_state}") + + if reset_state != target_state: + info.append(f"- CRITICAL: You must FIRST transition from {reset_state} to {target_state}!") + info.append(f"- Do NOT assume FSM will automatically reach {target_state}!") + + # 尝试找到状态转换路径 + transition_hint = self._find_state_transition_hint(full_content, reset_state, target_state) + if transition_hint: + info.append(f"- To reach {target_state}: {transition_hint}") + + if missing_condition: + info.append(f"- Missing condition: \"{missing_condition}\"") + # 分析条件优先级 + priority_info = self._analyze_condition_priority(full_content, target_state, missing_condition) + if priority_info: + info.extend(priority_info) + + return info + + def _find_reset_state(self, content: str) -> Optional[str]: + """从 DUT 代码中找到复位后的初始状态""" + # 查找复位逻辑中的状态赋值 + # 常见模式: if (reset) state <= IDLE; 或 state <= 0; + patterns = [ + r'if\s*\([^)]*reset[^)]*\)\s*state\s*<=\s*([A-Z][A-Z0-9_]*);', + r'if\s*\([^)]*reset[^)]*\)\s*state\s*<=\s*(\d+);', + r'if\s*\([^)]*rst[^)]*\)\s*state\s*<=\s*([A-Z][A-Z0-9_]*);', + ] + + for pattern in patterns: + match = re.search(pattern, content, re.IGNORECASE) + if match: + state = match.group(1) + # 如果是数字,尝试从参数中找对应的状态名 + if state.isdigit(): + # 查找参数定义 + param_match = re.search(r'parameter\s+([^;]+);', content) + if param_match: + params = param_match.group(1) + # 解析参数列表 + for param in params.split(','): + param = param.strip() + if '=' in param: + name, value = param.split('=') + if value.strip() == state: + return name.strip() + return state + + return None + + def _find_state_transition_hint(self, content: str, from_state: str, to_state: str) -> Optional[str]: + """找到从一个状态到另一个状态的转换条件""" + # 在 case 语句中查找 from_state 分支 + # 提取该分支下到 to_state 的转换条件 + + # 简单策略:查找 "next = TO_STATE" 或 "next <= TO_STATE" + pattern = rf'{from_state}\s*:.*?next\s*=?\s*{to_state}' + match = re.search(pattern, content, re.DOTALL) + + if match: + # 提取条件 + branch_code = match.group(0) + # 查找 if 条件 + if_match = re.search(r'if\s*\(([^)]+)\)\s*next\s*=?\s*' + to_state, branch_code) + if if_match: + return f"set condition: {if_match.group(1)}" + + # 查找 else if 条件 + elif_match = re.search(r'else\s+if\s*\(([^)]+)\)\s*next\s*=?\s*' + to_state, branch_code) + if elif_match: + return f"set condition: {elif_match.group(1)} (ensure earlier conditions are FALSE)" + + # 尝试反向查找:什么条件下会转换到目标状态 + trans_pattern = rf'(?:if|else\s+if)\s*\(([^)]+)\)\s*(?:next\s*=?\s*{to_state}|{to_state}\s*;)' + trans_match = re.search(trans_pattern, content) + if trans_match: + return f"set condition: {trans_match.group(1)}" + + return None + + def _analyze_condition_priority(self, content: str, state: str, missing_condition: str) -> List[str]: + """分析条件分支的优先级,找出需要排除的条件""" + info = [] + + if not state: + return info + + # 查找该状态下的所有条件分支 + # 提取 state: 后面的代码块 + state_block_pattern = rf'{state}\s*:(.*?)(?=[A-Z][A-Z0-9_]*\s*:|endcase|default:)' + match = re.search(state_block_pattern, content, re.DOTALL) + + if not match: + return info + + state_block = match.group(1) + + # 提取所有条件分支 + conditions = [] + for cond_match in re.finditer(r'(?:if|else\s+if)\s*\(([^)]+)\)', state_block): + conditions.append(cond_match.group(1).strip()) + + # 找到缺失条件在列表中的位置 + missing_idx = -1 + for i, cond in enumerate(conditions): + # 简化比较(去除空格) + if cond.replace(' ', '') in missing_condition.replace(' ', '') or \ + missing_condition.replace(' ', '') in cond.replace(' ', ''): + missing_idx = i + break + + if missing_idx > 0: + info.append(f"- This branch is condition #{missing_idx + 1} (lower priority)") + info.append(f"- You must make ALL earlier conditions FALSE:") + for i in range(missing_idx): + cond = conditions[i] + # 分析如何使条件为 FALSE + false_hint = self._get_false_hint(cond) + info.append(f" * \"{cond}\" must be FALSE → {false_hint}") + + return info + + def _get_false_hint(self, condition: str) -> str: + """分析如何使条件为 FALSE""" + condition = condition.strip() + + # 处理 !signal 形式 + if condition.startswith('!'): + signal = condition[1:].strip() + return f"set {signal} = 1" + + # 处理 signal 形式(布尔值) + if re.match(r'^[a-zA-Z_][a-zA-Z0-9_]*$', condition): + return f"set {condition} = 0" + + # 处理比较运算符 + if '==' in condition: + parts = condition.split('==') + if len(parts) == 2: + signal = parts[0].strip() + value = parts[1].strip() + if value.isdigit(): + return f"set {signal} != {value}" + + # 处理 >= 形式 + if '>=' in condition: + parts = condition.split('>=') + if len(parts) == 2: + signal = parts[0].strip() + value = parts[1].strip() + if value.isdigit(): + return f"set {signal} < {value}" + + # 处理 > 形式 + if '>' in condition and '>=' not in condition: + parts = condition.split('>') + if len(parts) == 2: + signal = parts[0].strip() + value = parts[1].strip() + return f"set {signal} <= {value}" + + return "analyze the condition logic" + + def _generate_semantic_context(self) -> str: + """ + [新增] 从语义分析结果生成 Prompt 上下文 + + 整合语义分析层 (Layer 0) 的输出,为 LLM 提供更精准的指导: + - FSM 状态转换图 + - 功能点重要性排序 + - 测试场景建议 + + Returns: + 语义上下文字符串,用于增强 Prompt + """ + if not self.semantic_result: + return "" + + context_parts = [] + + # 1. 模块基础信息 + module_name = self.semantic_result.get('module_name', '') + inputs = self.semantic_result.get('inputs', []) + outputs = self.semantic_result.get('outputs', []) + + if module_name: + context_parts.append(f"Module Name: {module_name}") + if inputs: + context_parts.append(f"Module Inputs: {', '.join(inputs)}") + if outputs: + context_parts.append(f"Module Outputs: {', '.join(outputs)}") + + # 2. FSM 信息(最关键) + fsm_info = self.semantic_result.get('fsm_info') + if fsm_info: + context_parts.append("") + context_parts.append("=== FSM STATE MACHINE DETAILS ===") + context_parts.append(f"State Variable: {fsm_info.get('state_variable', 'unknown')}") + + states = fsm_info.get('states', []) + if states: + context_parts.append(f"All States ({len(states)}): {', '.join(states)}") + + # 状态转换表 + transitions = fsm_info.get('transitions', {}) + if transitions: + context_parts.append("") + context_parts.append("=== STATE TRANSITION TABLE ===") + context_parts.append("Format: CURRENT_STATE --[CONDITION]--> NEXT_STATE") + context_parts.append("") + + for state, trans_list in transitions.items(): + for trans in trans_list: + condition = trans.get('condition', 'default') + next_state = trans.get('next_state', 'unknown') + if condition == 'default': + context_parts.append(f" {state} --[default]--> {next_state}") + else: + context_parts.append(f" {state} --[if ({condition})]--> {next_state}") + + # 添加状态转换路径分析 + context_parts.append("") + context_parts.append("=== STATE TRANSITION PATH HINTS ===") + reset_state = self._find_reset_state_from_fsm(fsm_info) + if reset_state: + context_parts.append(f"Initial State (after reset): {reset_state}") + context_parts.append("") + context_parts.append("IMPORTANT: To reach a target state, trace the path from reset:") + context_parts.append(" 1. Reset the DUT to initialize to the starting state") + context_parts.append(" 2. Apply inputs to trigger state transitions") + context_parts.append(" 3. Wait for the FSM to naturally reach the target state") + context_parts.append(" 4. THEN apply inputs to trigger the missing branch") + + # 3. 功能点优先级 + function_points = self.semantic_result.get('function_points', []) + if function_points: + context_parts.append("") + context_parts.append("=== FUNCTION POINTS (Ranked by Importance) ===") + + for i, fp in enumerate(function_points[:10]): # Top 10 + name = fp.get('name', 'unknown') + fp_type = fp.get('type', 'unknown') + importance = fp.get('importance', 0) + covered = fp.get('covered', False) + status = "✓ COVERED" if covered else "✗ NOT COVERED" + context_parts.append(f" {i+1}. [{status}] {name} ({fp_type}): importance={importance:.2f}") + + # 4. 测试场景建议 + test_scenarios = self.semantic_result.get('test_scenarios', []) + if test_scenarios: + context_parts.append("") + context_parts.append("=== RECOMMENDED TEST SCENARIOS ===") + + for i, ts in enumerate(test_scenarios[:5]): # Top 5 + name = ts.get('name', 'unknown') + description = ts.get('description', '') + priority = ts.get('priority', 0) + context_parts.append(f" {i+1}. {name}: {description} (priority={priority:.2f})") + + if context_parts: + return "\n".join(context_parts) + return "" + + def _find_reset_state_from_fsm(self, fsm_info: dict) -> Optional[str]: + """从 FSM 信息中推断复位后的初始状态""" + # 方法1:检查是否有明确的复位状态 + transitions = fsm_info.get('transitions', {}) + + # 复位后通常进入第一个定义的状态或特定名称的状态 + states = fsm_info.get('states', []) + + # 常见的初始状态命名 + initial_state_names = ['IDLE', 'INIT', 'RESET', 'START', 'BEGIN'] + + for name in initial_state_names: + if name in states: + return name + + # 如果没有找到,返回第一个状态 + if states: + return states[0] + + return None + + +# ============================================================================ +# TBInjector - 场景注入器 +# ============================================================================ +class TBInjector: + """ + 场景注入器 - 将LLM生成的测试代码注入到现有测试平台 + + 集成三层防护策略: + 1. Layer 1: Prompt约束(由CoverageParser处理) + 2. Layer 2: 智能代码转换 + 3. Layer 3: 质量评估和重试建议 + """ + + def __init__(self, tb_code): + """ + 初始化注入器 + + Args: + tb_code: 原始测试平台代码字符串 + """ + self.content = tb_code + self.validator = BlackBoxValidator() + self.validator._extract_signals_from_tb(tb_code) + self.last_validation_result = None + + def inject(self, new_code, iter_idx): + """ + 注入新的测试场景到测试平台 + + Args: + new_code: LLM生成的测试代码 + iter_idx: 迭代序号 + + Returns: + 修改后的测试平台代码 + """ + # Step 1: 预处理代码(包含三层防护) + scenario_code, result = self._preprocess_code(new_code, iter_idx) + + self.last_validation_result = result + + # 记录日志 + if result['violations']['critical']: + logger.warning(f"[CGA-{iter_idx}] Critical violations detected:") + for v in result['violations']['critical']: + logger.warning(f" - {v}") + + if result['violations']['warning']: + logger.info(f"[CGA-{iter_idx}] Warnings:") + for v in result['violations']['warning']: + logger.info(f" - {v}") + + if result['transformations']: + logger.info(f"[CGA-{iter_idx}] Code transformations applied:") + for t in result['transformations']: + logger.info(f" - {t['type']}: {t.get('original', 'N/A')[:50]}...") + + # Step 2: 构建场景块 + scenario_block = self._build_scenario_block(scenario_code, iter_idx) + + # Step 3: 注入到TB中 + modified_tb = self._inject_scenario(scenario_block) + + return modified_tb + + def should_retry(self): + """是否应该重试""" + if self.last_validation_result is None: + return False + return self.last_validation_result.get('should_retry', False) + + def get_quality_score(self): + """获取代码质量分数""" + if self.last_validation_result is None: + return 0 + return self.last_validation_result.get('quality_score', 0) + + def _preprocess_code(self, code, iter_idx): + """预处理LLM生成的代码 - 增强版,包含语法预检查""" + # 移除markdown标记 + code = re.sub(r'```(?:verilog|systemverilog|sv)?\n?', '', code) + code = re.sub(r'```', '', code) + + # 移除task包装 + code = re.sub(r'task\s+\w+\s*(?:\([^)]*\))?\s*;', '', code) + code = re.sub(r'endtask', '', code) + + # 移除$finish和$stop + code = re.sub(r'\$finish\s*;', '', code) + code = re.sub(r'\$stop\s*;', '', code) + + # 移除多余空行 + code = re.sub(r'\n\s*\n\s*\n', '\n\n', code) + + + # [修复] 移除 initial begin ... end 代码块 + # LLM 可能生成完整的 initial begin...end 块,但我们只需要其中的测试代码 + # 使用括号计数来正确匹配嵌套结构,而不是简单的正则表达式 + + initial_match = re.search(r'\binitial\s+begin\b', code, re.IGNORECASE) + if initial_match: + logger.warning(f"[CGA-{iter_idx}] Detected 'initial begin...end' block in generated code - this should not be included") + logger.warning(f"[CGA-{iter_idx}] Removing 'initial begin...end' wrapper, keeping only the test content") + + # 找到 initial begin 后的起始位置 + start_pos = initial_match.end() + + # 使用括号计数找到匹配的 end + begin_count = 1 # 已经遇到一个 begin (initial begin) + end_pos = start_pos + code_after_initial = code[start_pos:] + + for i, char in enumerate(code_after_initial): + # 检查是否是关键字 begin 或 end + remaining = code_after_initial[i:] + if re.match(r'\bbegin\b', remaining, re.IGNORECASE): + begin_count += 1 + elif re.match(r'\bend\b', remaining, re.IGNORECASE): + begin_count -= 1 + if begin_count == 0: + # 找到匹配的 end + end_pos = start_pos + i + break + + if begin_count == 0: + # 提取块内的内容 + inner_content = code[start_pos:end_pos].strip() + # 移除末尾的 end(如果有) + inner_content = re.sub(r'\bend\s*$', '', inner_content.strip()) + # 重建代码:移除 initial begin ... end 包装 + code = code[:initial_match.start()] + inner_content + code[end_pos + 3:] # +3 跳过 'end' + logger.info(f"[CGA-{iter_idx}] Successfully removed 'initial begin...end' wrapper") + else: + logger.warning(f"[CGA-{iter_idx}] Could not find matching 'end' for 'initial begin', keeping code as-is") + + code = re.sub(r'\n\s*\n\s*\n', '\n\n', code) + + + # 信号名自动修正(在验证之前) + code = self._auto_correct_signal_names(code) + + # 三层防护:黑盒约束验证和转换 + code, result = self.validator.validate_and_transform(code, self.content) + + # [新增] 第四层:Verilog 语法预检查 + # 提取完整 TB 中已声明的信号(不只是代码片段) + signal_widths = self._extract_signal_widths() + declared_signals = self._extract_declared_signals() + + # 调用语法检查,传入已声明信号列表 + syntax_result = self.validator.check_syntax_issues( + code, + signal_widths, + declared_signals=declared_signals + ) + + # 合并检查结果 + result['syntax_check'] = syntax_result + + # 记录语法问题日志 + if syntax_result['width_mismatch']: + logger.warning(f"[CGA-{iter_idx}] Width mismatch detected:") + for issue in syntax_result['width_mismatch']: + logger.warning(f" - {issue['message']}") + if 'suggestion' in issue: + logger.info(f" Suggestion: {issue['suggestion']}") + + if syntax_result['logic_issues']: + logger.warning(f"[CGA-{iter_idx}] Logic issues detected:") + for issue in syntax_result['logic_issues']: + logger.warning(f" - {issue['message']}") + if 'suggestion' in issue: + logger.info(f" Suggestion: {issue['suggestion']}") + + if syntax_result['syntax_warnings']: + for issue in syntax_result['syntax_warnings']: + if issue['severity'] == 'error': + logger.error(f"[CGA-{iter_idx}] Syntax error: {issue['message']}") + else: + logger.warning(f"[CGA-{iter_idx}] Syntax warning: {issue['message']}") + + # 如果语法检查发现问题,设置 should_retry + if syntax_result['should_retry']: + result['should_retry'] = True + logger.warning(f"[CGA-{iter_idx}] Syntax issues detected, recommend retry with corrected code") + + code = re.sub(r'\n\s*\n\s*\n', '\n\n', code) + + return code.strip(), result + + def _extract_declared_signals(self) -> set: + """从完整测试平台中提取所有已声明的信号""" + signals = set() + + # 匹配 reg [N:0] signal 或 wire [N:0] signal + for match in re.finditer(r'\b(reg|wire|logic)\s+(?:\[[^\]]+\]\s*)?(\w+)', self.content): + signals.add(match.group(2)) + + # 匹配 input/output 声明 + for match in re.finditer(r'\b(input|output|inout)\s+(?:\[[^\]]+\]\s*)?(\w+)', self.content): + signals.add(match.group(2)) + + # 匹配模块端口连接中的信号 + for match in re.finditer(r'\.(\w+)\s*\(\s*(\w+)\s*\)', self.content): + signals.add(match.group(2)) # 添加连接的信号名 + + return signals + + def _extract_signal_widths(self) -> Dict[str, int]: + """从测试平台中提取信号位宽信息""" + widths = {} + + # 匹配 reg [N:0] signal 或 wire [N:0] signal + width_pattern = re.compile(r'\b(reg|wire)\s+\[(\d+):(\d+)\]\s+(\w+)') + + for match in width_pattern.finditer(self.content): + high = int(match.group(2)) + low = int(match.group(3)) + width = high - low + 1 + signal = match.group(4) + widths[signal] = width + + # 匹配无位宽声明的信号(默认 1 位) + single_bit_pattern = re.compile(r'\b(reg|wire)\s+(?!.*\[)(\w+)\s*;') + for match in single_bit_pattern.finditer(self.content): + signal = match.group(2) + if signal not in widths: + widths[signal] = 1 + + return widths + + def _auto_correct_signal_names(self, code: str) -> str: + """自动修正信号名错误""" + corrections = [] + + # 获取正确的复位信号名 + reset_signal = self.validator._find_reset_signal() + + # 如果正确的复位信号不是 'reset',则修正所有 'reset' 引用 + if reset_signal != "reset": + # 匹配独立的 'reset' 单词(不包括 'areset', 'rst_n' 等) + pattern = r'\breset\b(?!\w)' + matches = re.findall(pattern, code) + if matches: + code = re.sub(pattern, reset_signal, code) + corrections.append(f"reset -> {reset_signal} ({len(matches)} occurrences)") + + # 检查是否有使用 'rst' 但正确信号是 'areset' 的情况 + if reset_signal == "areset": + pattern = r'\brst\b(?!\w)' + matches = re.findall(pattern, code) + if matches: + code = re.sub(pattern, reset_signal, code) + corrections.append(f"rst -> {reset_signal} ({len(matches)} occurrences)") + + # 检查是否使用了不存在的信号 + for signal in re.findall(r'\b(\w+)\s*=', code): + signal = signal.strip() + # 跳过已知的合法信号 + if signal in self.validator.dut_inputs: + continue + # 检查是否是复位信号的别名 + if signal.lower() in ['reset', 'rst', 'rst_n', 'rst_b'] and reset_signal != signal: + code = re.sub(rf'\b{signal}\b', reset_signal, code) + corrections.append(f"{signal} -> {reset_signal}") + + # [新增] 检测并修正类似 input_signal_N 的通用命名模式 + # 这是 LLM 总是使用的通用名称,需要映射到实际信号 + valid_inputs = [s for s in self.validator.dut_inputs if 'clk' not in s.lower()] + + # 处理 input_signal_1, input_signal_2, ... 等模式 + for match in re.finditer(r'\b(input_signal_\d+)\b', code): + wrong_signal = match.group(1) + if valid_inputs: + # 提取数字后缀,尝试按顺序映射 + num_match = re.search(r'(\d+)$', wrong_signal) + if num_match: + idx = int(num_match.group(1)) - 1 # input_signal_1 -> index 0 + if idx < len(valid_inputs): + correct_signal = valid_inputs[idx] + else: + correct_signal = valid_inputs[0] # fallback + else: + correct_signal = valid_inputs[0] + + code = re.sub(rf'\b{re.escape(wrong_signal)}\b', correct_signal, code) + corrections.append(f"{wrong_signal} -> {correct_signal} (generic name mapped)") + + # 处理 in, data 等太通用的名称 + for generic_name in ['in', 'data', 'input', 'din']: + if re.search(rf'\b{generic_name}\s*=', code): + if valid_inputs: + # 使用第一个有效的输入信号作为替代 + correct_signal = valid_inputs[0] + code = re.sub(rf'\b{generic_name}\s*=', f'{correct_signal} =', code) + corrections.append(f"{generic_name} -> {correct_signal} (too generic)") + + if corrections: + logger.info(f"[Signal Correction] Applied corrections: {'; '.join(corrections)}") + + return code + + def _build_scenario_block(self, scenario_code, iter_idx): + """构建完整的场景代码块""" + # 格式化缩进 + lines = scenario_code.split('\n') + formatted_lines = [] + for line in lines: + stripped = line.strip() + if stripped: + formatted_lines.append(f" {stripped}") + formatted_code = '\n'.join(formatted_lines) + + # 检测输出信号用于日志 + output_signals = self._detect_output_signals() + output_log = self._generate_output_log(output_signals, iter_idx) + + # 构建完整块 + block = f''' + // ========== CGA Iteration {iter_idx} ========== + scenario = 100 + {iter_idx}; + // Reset signals to safe state +{self._generate_signal_reset()} + #5; + // CGA generated test sequence: +{formatted_code} + // Log results +{output_log} + // ============================================== +''' + return block + + def _detect_output_signals(self): + """检测DUT的输出信号""" + outputs = [] + wire_pattern = re.compile(r'wire\s+(?:\[[\d:]+\]\s*)?(\w+)\s*;') + for match in wire_pattern.finditer(self.content): + signal = match.group(1) + if signal.lower() not in ['clk', 'clock', 'rst', 'reset', 'areset']: + outputs.append(signal) + return outputs + + def _generate_signal_reset(self): + """生成信号重置代码""" + inputs = [] + reg_pattern = re.compile(r'reg\s+(?:\[[\d:]+\]\s*)?(\w+)\s*;') + for match in reg_pattern.finditer(self.content): + signal = match.group(1) + if signal.lower() not in ['clk', 'clock', 'file', 'scenario']: + inputs.append(signal) + + if inputs: + return " " + "; ".join([f"{sig} = 0" for sig in inputs]) + ";" + return " // No input signals to reset" + + def _generate_output_log(self, signals, iter_idx): + """生成输出日志代码""" + if not signals: + return f' $display("[CGA-{iter_idx}] Scenario executed");' + + sig_names = ", ".join(signals) + format_str = ", ".join(["%b"] * len(signals)) + + return f' $fdisplay(file, "[CGA-{iter_idx}] {sig_names} = {format_str}", {sig_names});' + + def _inject_scenario(self, scenario_block): + """将场景块注入到测试平台""" + modified_tb = self.content + + # 策略:如果有 $fclose,在其之前插入 + if "$fclose" in modified_tb: + modified_tb = re.sub( + r'(\s*)(\$fclose\s*\([^)]+\)\s*;)', + scenario_block + r'\1\2', + modified_tb, + count=1 + ) + elif "$finish" in modified_tb: + # 否则在 $finish 之前插入 + modified_tb = modified_tb.replace( + "$finish;", + scenario_block + "\n $finish;" + ) + else: + # 兜底:在最后一个 end 之前插入 + last_end = modified_tb.rfind("end") + if last_end != -1: + modified_tb = modified_tb[:last_end] + scenario_block + modified_tb[last_end:] + + return modified_tb \ No newline at end of file diff --git a/autoline/diversity_injector.py b/autoline/diversity_injector.py new file mode 100644 index 0000000..c332598 --- /dev/null +++ b/autoline/diversity_injector.py @@ -0,0 +1,601 @@ +""" +Description : Diversity Constraint Injector (Layer 1) + - Analyze existing test sequences + - Detect overused patterns + - Generate diversity constraints for Prompt + - Recommend new test scenarios +Author : CGA Enhancement Project +Time : 2026/03/16 +""" + +import logging +import re +from typing import List, Dict, Optional, Any, Tuple, Set +from dataclasses import dataclass, field +from enum import Enum + +# 支持两种导入方式:包导入和直接加载 +try: + from .test_history import ( + TestHistoryManager, + TestRecord, + InputSequence, + SequencePattern + ) + +except ImportError: + from test_history import ( + TestHistoryManager, + TestRecord, + InputSequence, + SequencePattern + ) + +logger = logging.getLogger(__name__) + + +# ============================================================================ +# 配置常量 +# ============================================================================ + +class DiversityConfig: + """多样性约束配置""" + + # 编辑距离阈值 + MIN_EDIT_DISTANCE = 3 + + # 模式过度使用阈值 + OVERUSE_THRESHOLD = 3 + + # 新场景推荐数量 + NEW_SCENARIO_COUNT = 3 + + # 序列长度限制(用于约束生成) + MAX_SEQUENCE_LENGTH = 10 + + # 多样性得分权重 + PATTERN_WEIGHT = 0.4 + EDIT_DISTANCE_WEIGHT = 0.3 + COVERAGE_WEIGHT = 0.3 + + +# ============================================================================ +# 约束类型定义 +# ============================================================================ + +class ConstraintType(Enum): + """约束类型枚举""" + FORBID_SEQUENCE = "forbid_sequence" # 禁止特定序列 + MIN_EDIT_DISTANCE = "min_edit_distance" # 最小编辑距离 + AVOID_PATTERN = "avoid_pattern" # 革免模式 + TRY_SCENARIO = "try_scenario" # 尝试新场景 + EXPLORE_RANGE = "explore_range" # 探索范围 + + +# ============================================================================ +# 约束数据结构 +# ============================================================================ + +@dataclass +class DiversityConstraint: + """ + 多样性约束 + + Attributes: + constraint_type: 约束类型 + description: 约束描述 + details: 详细信息 + priority: 优先级 (1-5, 5最高) + """ + constraint_type: ConstraintType + description: str + details: Dict[str, Any] = field(default_factory=dict) + priority: int = 3 + + def to_prompt_text(self) -> str: + """转换为Prompt文本""" + if self.constraint_type == ConstraintType.FORBID_SEQUENCE: + return f"- AVOID using this sequence pattern: {self.details.get('pattern', 'unknown')}" + + elif self.constraint_type == ConstraintType.MIN_EDIT_DISTANCE: + return f"- Your test sequence MUST differ from previous tests (edit distance >= {self.details.get('min_distance', 3)})" + + elif self.constraint_type == ConstraintType.AVOID_PATTERN: + signal = self.details.get('signal', '') + pattern = self.details.get('pattern', '') + return f"- AVOID the pattern '{pattern}' for signal '{signal}' (already used {self.details.get('count', 0)} times)" + + elif self.constraint_type == ConstraintType.TRY_SCENARIO: + return f"- TRY this new approach: {self.details.get('scenario', 'unknown')}" + + elif self.constraint_type == ConstraintType.EXPLORE_RANGE: + return f"- EXPLORE values in range [{self.details.get('min', 0)}, {self.details.get('max', 255)}] for {self.details.get('signal', 'signal')}" + + return f"- {self.description}" +# ============================================================================ +# 序列分析器 +# ============================================================================ + +class SequenceAnalyzer: + """ + 序列分析器 + + 分析输入序列的特征和模式 + """ + + @staticmethod + def extract_value_range(values: List[Tuple[int, Any]]) -> Tuple[Any, Any]: + """提取值范围""" + if not values: + return (0, 0) + + numeric_values = [] + for _, v in values: + # 尝试转换为数值 + if isinstance(v, (int, float)): + numeric_values.append(v) + elif isinstance(v, str): + # 处理 '0, '1, 'x 等 + if v in ['0', '1', 'x', 'z']: + numeric_values.append(int(v) if v.isdigit() else 0) + # 处理带位宽的值 + match = re.match(r"(\d+)'[bdh]([0-9a-fA-fA-FxXzZ_]+)", v) + if match: + try: + numeric_values.append(int(match.group(2), 16)) + except: + pass + + if numeric_values: + return (min(numeric_values), max(numeric_values)) + return (0, 0) + + @staticmethod + def detect_transition_pattern(values: List[Tuple[int, Any]]) -> str: + """检测转换模式""" + if len(values) < 2: + return "single" + + # 提取值序列 + val_seq = [v for _, v in values] + + # 检测递增 + if all(str(val_seq[i]) <= str(val_seq[i+1]) for i in range(len(val_seq)-1)): + return "incremental" + + # 检测递减 + if all(str(val_seq[i]) >= str(val_seq[i+1]) for i in range(len(val_seq)-1)): + return "decremental" + + # 检测交替 + if len(val_seq) >= 4: + if val_seq[0] == val_seq[2] and val_seq[1] == val_seq[3]: + return "alternating" + + # 检测脉冲(单个变化后恢复) + if len(val_seq) == 3 and val_seq[0] == val_seq[2] != val_seq[1]: + return "pulse" + + return "random" + + @staticmethod + def calculate_sequence_length(code: str) -> int: + """计算代码中的操作序列长度""" + # 统计赋值语句数量 + assignments = len(re.findall(r'\w+\s*=\s*\S+\s*;', code)) + # 统计repeat语句 + repeats = re.findall(r'repeat\s*\(\s*(\d+)\s*\)', code) + repeat_cycles = sum(int(r) for r in repeats) + + return assignments + repeat_cycles + + +# ============================================================================ +# 场景推荐器 +# ============================================================================ + +class ScenarioRecommender: + """ + 场景推荐器 + + 根据历史记录和未覆盖功能点推荐新测试场景 + """ + + # 场景模板 + SCENARIO_TEMPLATES = { + 'fsm': [ + "Test state transition from {state_a} to {state_b}", + "Test illegal state transition handling", + "Test state machine reset behavior", + "Test state holding under stable inputs" + ], + 'counter': [ + "Test counter overflow behavior (count to max value)", + "Test counter underflow (if applicable)", + "Test counter reset during counting", + "Test counter enable/disable control" + ], + 'branch': [ + "Test boundary condition: {condition} at threshold", + "Test all branches of nested if-else", + "Test case statement with all possible values" + ], + 'protocol': [ + "Test handshake timeout scenario", + "Test back-to-back transactions", + "Test protocol violation handling" + ], + 'general': [ + "Apply random input patterns for extended duration", + "Test with boundary values (all 0s, all 1s)", + "Test rapid signal transitions", + "Test power-on/reset sequence variations" + ] + } + + def __init__(self, history_manager: TestHistoryManager): + self.history = history_manager + + def recommend_scenarios(self, + uncovered_functions: List[Dict], + covered_patterns: Set[str] = None) -> List[str]: + """ + 推荐新的测试场景 + + Args: + uncovered_functions: 未覆盖的功能点列表 + covered_patterns: 已覆盖的模式集合 + + Returns: + 推荐场景列表 + """ + recommendations = [] + covered_patterns = covered_patterns or set() + + # 基于未覆盖功能点推荐 + for func in uncovered_functions[:3]: + func_type = func.get('type', 'general') + func_name = func.get('name', '') + + templates = self.SCENARIO_TEMPLATES.get(func_type, self.SCENARIO_TEMPLATES['general']) + + for template in templates[:1]: # 每个功能点取一个模板 + scenario = self._fill_template(template, func) + if scenario not in covered_patterns: + recommendations.append(scenario) + + # 基于历史分析推荐 + if self.history.records: + # 分析已使用的场景类型 + used_patterns = set() + for record in self.history.records: + for seq in record.input_sequences: + pattern = SequenceAnalyzer.detect_transition_pattern(seq.values) + used_patterns.add(pattern) + + # 推荐未使用的场景类型 + all_patterns = {'incremental', 'decremental', 'alternating', 'pulse', 'random'} + unused_patterns = all_patterns - used_patterns + + + if unused_patterns: + recommendations.append(f"Try {list(unused_patterns)[0]} input pattern (different from your usual approach)") + + # 确保有足够的推荐 + while len(recommendations) < DiversityConfig.NEW_SCENARIO_COUNT: + recommendations.append("Explore a completely different input sequence than before") + + return recommendations[:DiversityConfig.NEW_SCENARIO_COUNT] + + def _fill_template(self, template: str, func: Dict) -> str: + """填充场景模板""" + result = template + + # 替换占位符 + if '{state_a}' in template or '{state_b}' in template: + states = func.get('states', ['STATE_A', 'STATE_B']) + if len(states) >= 2: + result = result.replace('{state_a}', states[0]) + result = result.replace('{state_b}', states[1]) + + if '{condition}' in template: + condition = func.get('condition', 'signal') + result = result.replace('{condition}', condition) + + return result + + +# ============================================================================ +# 约束生成器 +# ============================================================================ + +class ConstraintGenerator: + """ + 约束生成器 + + 根据历史分析生成多样性约束 + """ + + def __init__(self, history_manager: TestHistoryManager): + self.history = history_manager + self.analyzer = SequenceAnalyzer() + + def generate_constraints(self, + target_function: str = None, + uncovered_functions: List[Dict] = None) -> List[DiversityConstraint]: + """ + 生成多样性约束 + + Args: + target_function: 当前目标功能点 + uncovered_functions: 未覆盖功能点列表 + + Returns: + 约束列表 + """ + constraints = [] + + if not self.history.records: + return constraints + + # 1. 生成过度使用模式约束 + overused = self.history.get_overused_patterns(DiversityConfig.OVERUSE_THRESHOLD) + for pattern in overused[:3]: # 最多3个 + constraints.append(DiversityConstraint( + constraint_type=ConstraintType.AVOID_PATTERN, + description=f"Avoid overused pattern for {pattern.signal_name}", + details={ + 'signal': pattern.signal_name, + 'pattern': pattern.pattern, + 'count': pattern.count + }, + priority=5 + )) + + # 2. 生成编辑距离约束 + recent_count = min(5, len(self.history.records)) + if recent_count > 00: + constraints.append(DiversityConstraint( + constraint_type=ConstraintType.MIN_EDIT_DISTANCE, + description="Maintain minimum edit distance from recent tests", + details={ + 'min_distance': DiversityConfig.MIN_EDIT_DISTANCE, + 'reference_count': recent_count + }, + priority=4 + )) + + # 3. 生成值范围探索约束 + if uncovered_functions: + for func in uncovered_functions[:2]: + # 根据功能点类型生成范围约束 + if func.get('type') == 'counter': + max_val = func.get('max_value', 255) + constraints.append(DiversityConstraint( + constraint_type=ConstraintType.EXPLORE_RANGE, + description=f"Explore counter boundary values", + details={ + 'signal': func.get('name', 'counter'), + 'min': 0, + 'max': max_val + }, + priority=3 + )) + + # 按优先级排序 + constraints.sort(key=lambda c: c.priority, reverse=True) + + return constraints + + def generate_forbidden_sequence_prompt(self) -> str: + """生成禁止序列提示""" + overused = self.history.get_overused_patterns(DiversityConfig.OVERUSE_THRESHOLD) + + if not overused: + return "" + + lines = ["[DIVERSITY CONSTRAINTS - AVOID THESE OVERUSED PATTERNS]"] + + for i, pattern in enumerate(overused[:5], 1): + lines.append(f"{i}. Signal '{pattern.signal_name}': {pattern.pattern[:50]}") + lines.append(f" (This pattern has been used {pattern.count} times already)") + + lines.append("\nPlease create a DIFFERENT input sequence to improve test diversity.") + + return "\n".join(lines) + + +# ============================================================================ +# 多样性约束注入器(主入口) +# ============================================================================ + +class DiversityInjector: + """ + 多样性约束注入器 - 第1层主入口 + + 整合序列分析、模式检测、约束生成,提供统一的多样性约束接口 + """ + + def __init__(self, history_manager: TestHistoryManager = None): + """ + Args: + history_manager: 测试历史管理器 + """ + self.history = history_manager or TestHistoryManager() + self.constraint_generator = ConstraintGenerator(self.history) + self.scenario_recommender = ScenarioRecommender(self.history) + + def inject_diversity_constraints(self, + prompt: str, + target_function: str = None, + uncovered_functions: List[Dict] = None) -> str: + """ + 将多样性约束注入到Prompt中 + + Args: + prompt: 废始Prompt + target_function: 当前目标功能点 + uncovered_functions: 未覆盖功能点列表 + + Returns: + 注入约束后的Prompt + """ + if not self.history.records: + return prompt # 没有历史记录时不注入 + + # 生成约束 + constraints = self.constraint_generator.generate_constraints( + target_function=target_function, + uncovered_functions=uncovered_functions + ) + + # 生成推荐场景 + recommendations = self.scenario_recommender.recommend_scenarios( + uncovered_functions=uncovered_functions or [] + ) + + # 构建约束文本 + constraint_text = self._build_constraint_section(constraints, recommendations) + + # 找到插入点(在 [OUTPUT REQUIREMENTS] 之前插入) + insert_marker = "[OUTPUT REQUIREMENTS" + if insert_marker in prompt: + parts = prompt.split(insert_marker, 1) + enhanced_prompt = parts[0] + constraint_text + "\n\n" + insert_marker + parts[1] + else: + # 如果找不到标记,追加到末尾 + enhanced_prompt = prompt + "\n\n" + constraint_text + + return enhanced_prompt + + def _build_constraint_section(self, + constraints: List[DiversityConstraint], + recommendations: List[str]) -> str: + """构建约束章节""" + lines = [] + lines.append("[DIVERSITY CONSTRAINTS - CRITICAL]") + lines.append("To improve test effectiveness, follow these diversity requirements:") + lines.append("") + + # 添加约束 + for constraint in constraints: + lines.append(constraint.to_prompt_text()) + + lines.append("") + + # 添加推荐场景 + if recommendations: + lines.append("[RECOMMENDED NEW APPROACHES]") + for i, rec in enumerate(recommendations, 1): + lines.append(f"{i}. {rec}") + + lines.append("") + lines.append("IMPORTANT: Repeated test patterns reduce coverage improvement efficiency.") + lines.append("Generate a DISTINCTLY DIFFERENT test sequence from previous attempts.") + + return "\n".join(lines) + + def get_diversity_context(self) -> str: + """获取多样性上下文信息(用于Prompt)""" + if not self.history.records: + return "" + + stats = self.history.get_statistics() + overused = self.history.get_overused_patterns(DiversityConfig.OVERUSE_THRESHOLD) + + context_lines = [] + context_lines.append(f"Test History: {stats['total_tests']} tests generated") + context_lines.append(f"Unique Patterns: {stats['total_patterns']}") + + if overused: + context_lines.append(f"Overused Patterns: {len(overused)} (avoid these)") + + return "\n".join(context_lines) + + def evaluate_diversity(self, + new_code: str, + known_signals: List[str] = None) -> Dict[str, float]: + """ + 评估新代码的多样性 + + Args: + new_code: 新生成的测试代码 + known_signals: 已知信号列表 + + Returns: + 多样性评估结果 + """ + results = {} + + # 1. 序列多样性 + if known_signals: + self.history.sequence_extractor.set_known_signals(known_signals) + new_sequences = self.history.sequence_extractor.extract(new_code) + results['sequence_diversity'] = self.history.calculate_sequence_diversity(new_sequences) + + # 2. 编辑距离多样性 + results['edit_distance_diversity'] = self.history.calculate_edit_distance_diversity(new_code) + + # 3. 综合得分 + results['overall_diversity'] = ( + DiversityConfig.PATTERN_WEIGHT * results['sequence_diversity'] + + DiversityConfig.EDIT_DISTANCE_WEIGHT * results['edit_distance_diversity'] + ) + + return results + + def record_test(self, + code: str, + target_function: str = "", + coverage_score: float = 0.0, + success: bool = False, + iteration: int = 0, + known_signals: List[str] = None) -> TestRecord: + """ + 记录新的测试用例 + + Args: + code: 测试代码 + target_function: 目标功能点 + coverage_score: 覆盖率分数 + success: 是否成功 + iteration: 迭代次数 + known_signals: 已知信号列表 + + Returns: + 测试记录 + """ + return self.history.add_record( + code=code, + target_function=target_function, + coverage_score=coverage_score, + success=success, + iteration=iteration, + known_signals=known_signals + ) + + def get_statistics(self) -> Dict[str, Any]: + """获取统计信息""" + return self.history.get_statistics() + + def generate_diversity_report(self) -> str: + """生成多样性报告""" + return self.history.get_diversity_report() + + +# ============================================================================ +# 便捷函数 +# ============================================================================ + +def create_diversity_injector(history_file: str = None) -> DiversityInjector: + """ + 创建多样性约束注入器 + + Args: + history_file: 屆史记录文件路径 + + Returns: + 初始化完成的多样性约束注入器 + """ + history_manager = TestHistoryManager(history_file=history_file) + return DiversityInjector(history_manager=history_manager) diff --git a/autoline/energy_allocator.py b/autoline/energy_allocator.py new file mode 100644 index 0000000..e007c5b --- /dev/null +++ b/autoline/energy_allocator.py @@ -0,0 +1,787 @@ +""" +Description : Energy Allocation Layer (Layer 4) + - Adaptive Resource Scheduling + - Dynamic energy distribution based on function point importance +Author : CGA Enhancement Project +Time : 2026/03/11 +""" + +import logging +from typing import List, Dict, Optional, Any, Tuple +from dataclasses import dataclass, field +from enum import Enum + +logger = logging.getLogger(__name__) + + +# ============================================================================ +# 数据结构定义 +# ============================================================================ + +class EnergyState(Enum): + """能量状态枚举""" + ACTIVE = "active" # 活跃,有剩余能量 + DEPLETED = "depleted" # 能量耗尽 + COMPLETED = "completed" # 已完成覆盖 + SUSPENDED = "suspended" # 暂停(连续失败过多) + + +@dataclass +class EnergyAllocation: + """ + 能量分配记录 + + Attributes: + function_point: 功能点名称 + importance: 重要性评分 (0.0 - 1.0) + allocated: 分配的总能量 + consumed: 已消耗的能量 + remaining: 剩余能量 + consecutive_failures: 连续失败次数 + state: 当前能量状态 + """ + function_point: str + importance: float + allocated: float = 0.0 + consumed: float = 0.0 + remaining: float = 0.0 + consecutive_failures: int = 0 + state: EnergyState = EnergyState.ACTIVE + total_attempts: int = 0 + successful_attempts: int = 0 + + +@dataclass +class GenerationResult: + """ + 生成结果记录 + + Attributes: + function_point: 目标功能点 + success: 是否成功覆盖 + coverage_delta: 覆盖率变化 + energy_cost: 消耗的能量 + code_generated: 生成的代码 + quality_score: 代码质量分数 + """ + function_point: str + success: bool + coverage_delta: float = 0.0 + energy_cost: float = 1.0 + code_generated: str = "" + quality_score: float = 0.0 + + +# ============================================================================ +# 能量初始化器 +# ============================================================================ + +class EnergyInitializer: + """ + 能量初始化器 + + 根据总能量预算和功能点重要性评分,初始化各功能点的能量分配 + """ + + # 默认配置 + DEFAULT_TOTAL_ENERGY = 10.0 # 默认总能量(对应最大迭代次数) + MIN_ENERGY_PER_FP = 1.0 # 每个功能点最小能量 + ENERGY_BUFFER_RATIO = 0.1 # 能量缓冲比例(保留用于重分配) + + def __init__(self, + total_energy: float = None, + min_energy: float = None, + buffer_ratio: float = None): + """ + Args: + total_energy: 总能量预算(默认为 max_iter) + min_energy: 每个功能点最小能量 + buffer_ratio: 能量缓冲比例 + """ + self.total_energy = total_energy or self.DEFAULT_TOTAL_ENERGY + self.min_energy = min_energy or self.MIN_ENERGY_PER_FP + self.buffer_ratio = buffer_ratio or self.ENERGY_BUFFER_RATIO + + def initialize(self, + function_points: List[Dict], + max_iterations: int = None) -> Dict[str, EnergyAllocation]: + """ + 初始化能量分配 + + Args: + function_points: 功能点列表,每个元素包含 name, importance, covered 等 + max_iterations: 最大迭代次数(用于设置总能量) + + Returns: + 功能点名称 -> 能量分配记录 的字典 + """ + # 如果提供了最大迭代次数,使用它作为总能量 + if max_iterations: + self.total_energy = float(max_iterations) + + # 过滤出未覆盖的功能点 + uncovered_fps = [fp for fp in function_points if not fp.get('covered', False)] + + if not uncovered_fps: + logger.info("All function points are covered. No energy allocation needed.") + return {} + + # 计算总重要性 + total_importance = sum(fp.get('importance', 0.5) for fp in uncovered_fps) + + # 预留缓冲能量 + buffer_energy = self.total_energy * self.buffer_ratio + available_energy = self.total_energy - buffer_energy + + # 按重要性比例分配能量 + allocations = {} + + for fp in uncovered_fps: + name = fp.get('name', 'unknown') + importance = fp.get('importance', 0.5) + + # 按比例计算分配能量,但不少于最小值 + if total_importance > 0: + proportional_energy = (importance / total_importance) * available_energy + else: + proportional_energy = available_energy / len(uncovered_fps) + + allocated = max(self.min_energy, proportional_energy) + + allocations[name] = EnergyAllocation( + function_point=name, + importance=importance, + allocated=allocated, + consumed=0.0, + remaining=allocated, + consecutive_failures=0, + state=EnergyState.ACTIVE, + total_attempts=0, + successful_attempts=0 + ) + + # 记录分配情况 + logger.info(f"Energy initialized: total={self.total_energy:.1f}, " + f"allocated={sum(a.allocated for a in allocations.values()):.1f}, " + f"buffer={buffer_energy:.1f}, " + f"targets={len(allocations)}") + + return allocations + + +# ============================================================================ +# 目标选择器 +# ============================================================================ + +class TargetSelector: + """ + 目标选择器 + + 选择下一个需要生成测试的目标功能点 + 采用优先级策略:重要性 × (剩余能量/分配能量) + """ + + # 连续失败阈值 + MAX_CONSECUTIVE_FAILURES = 3 + + def __init__(self, allocations: Dict[str, EnergyAllocation]): + """ + Args: + allocations: 能量分配字典 + """ + self.allocations = allocations + + def select_next_target(self) -> Optional[EnergyAllocation]: + """ + 选择下一个目标功能点 + + 优先级计算:importance × (remaining / allocated) × (1 / (1 + consecutive_failures)) + + Returns: + 选中的能量分配记录,如果没有可用目标则返回 None + """ + # 筛选候选:未覆盖、有剩余能量、非暂停状态 + candidates = [ + alloc for alloc in self.allocations.values() + if alloc.state == EnergyState.ACTIVE + and alloc.remaining > 0 + ] + + if not candidates: + logger.info("No active targets with remaining energy.") + return None + + # 计算优先级并排序 + def calculate_priority(alloc: EnergyAllocation) -> float: + # 重要性权重 + importance_weight = alloc.importance + + # 能量剩余比例 + energy_ratio = alloc.remaining / alloc.allocated if alloc.allocated > 0 else 0 + + # 失败惩罚因子 + failure_penalty = 1.0 / (1.0 + alloc.consecutive_failures * 0.5) + + # 综合优先级 + priority = importance_weight * energy_ratio * failure_penalty + return priority + + candidates.sort(key=calculate_priority, reverse=True) + + selected = candidates[0] + logger.debug(f"Selected target: {selected.function_point} " + f"(importance={selected.importance:.2f}, " + f"remaining={selected.remaining:.1f}, " + f"failures={selected.consecutive_failures})") + + return selected + + def get_candidates_count(self) -> int: + """获取候选目标数量""" + return len([a for a in self.allocations.values() + if a.state == EnergyState.ACTIVE and a.remaining > 0]) + + def get_top_candidates(self, n: int = 3) -> List[EnergyAllocation]: + """获取优先级最高的 N 个候选目标""" + candidates = [ + alloc for alloc in self.allocations.values() + if alloc.state == EnergyState.ACTIVE and alloc.remaining > 0 + ] + + def calculate_priority(alloc: EnergyAllocation) -> float: + importance_weight = alloc.importance + energy_ratio = alloc.remaining / alloc.allocated if alloc.allocated > 0 else 0 + failure_penalty = 1.0 / (1.0 + alloc.consecutive_failures * 0.5) + return importance_weight * energy_ratio * failure_penalty + + candidates.sort(key=calculate_priority, reverse=True) + return candidates[:n] + + +# ============================================================================ +# 能量消耗跟踪器 +# ============================================================================ + +class EnergyConsumptionTracker: + """ + 能量消耗跟踪器 + + 跟踪每次生成尝试的能量消耗,根据结果更新状态 + """ + + # 能量衰减因子(连续失败时) + ENERGY_DECAY_FACTOR = 0.7 + + def __init__(self, allocations: Dict[str, EnergyAllocation]): + """ + Args: + allocations: 能量分配字典 + """ + self.allocations = allocations + self.history: List[GenerationResult] = [] + + def record_generation(self, result: GenerationResult) -> Dict[str, Any]: + """ + 记录一次生成尝试 + + Args: + result: 生成结果 + + Returns: + 更新后的状态信息 + """ + self.history.append(result) + + fp_name = result.function_point + if fp_name not in self.allocations: + logger.warning(f"Unknown function point: {fp_name}") + return {'status': 'unknown', 'message': 'Unknown function point'} + + alloc = self.allocations[fp_name] + alloc.total_attempts += 1 + + # 消耗能量 + energy_cost = result.energy_cost + alloc.consumed += energy_cost + alloc.remaining = max(0, alloc.remaining - energy_cost) + + if result.success: + # 成功:重置失败计数,标记完成 + alloc.consecutive_failures = 0 + alloc.successful_attempts += 1 + alloc.state = EnergyState.COMPLETED + + logger.info(f"[SUCCESS] Target covered: {fp_name} (attempts={alloc.total_attempts}, " + f"energy_used={alloc.consumed:.1f})") + + return { + 'status': 'completed', + 'function_point': fp_name, + 'attempts': alloc.total_attempts, + 'energy_used': alloc.consumed + } + else: + # 失败:增加失败计数 + alloc.consecutive_failures += 1 + + # 检查是否需要降低能量或暂停 + if alloc.consecutive_failures >= 3: + # 能量减半 + old_remaining = alloc.remaining + alloc.remaining *= self.ENERGY_DECAY_FACTOR + + logger.warning(f"Consecutive failures for {fp_name}: {alloc.consecutive_failures}. " + f"Energy reduced: {old_remaining:.1f} -> {alloc.remaining:.1f}") + + # 如果剩余能量过低,暂停 + if alloc.remaining < 0.5: + alloc.state = EnergyState.SUSPENDED + logger.warning(f"Target suspended due to low energy: {fp_name}") + + return { + 'status': 'suspended', + 'function_point': fp_name, + 'consecutive_failures': alloc.consecutive_failures, + 'remaining_energy': alloc.remaining + } + + # 检查能量是否耗尽 + if alloc.remaining <= 0: + alloc.state = EnergyState.DEPLETED + logger.warning(f"Target depleted: {fp_name}") + + return { + 'status': 'depleted', + 'function_point': fp_name, + 'total_attempts': alloc.total_attempts + } + + return { + 'status': 'failed', + 'function_point': fp_name, + 'consecutive_failures': alloc.consecutive_failures, + 'remaining_energy': alloc.remaining + } + + def get_statistics(self) -> Dict[str, Any]: + """获取统计信息""" + total = len(self.history) + successful = sum(1 for r in self.history if r.success) + + energy_by_fp = {} + for result in self.history: + fp = result.function_point + if fp not in energy_by_fp: + energy_by_fp[fp] = {'consumed': 0, 'attempts': 0, 'success': False} + energy_by_fp[fp]['consumed'] += result.energy_cost + energy_by_fp[fp]['attempts'] += 1 + if result.success: + energy_by_fp[fp]['success'] = True + + return { + 'total_attempts': total, + 'successful_attempts': successful, + 'success_rate': successful / total if total > 0 else 0, + 'energy_by_function_point': energy_by_fp + } + + +# ============================================================================ +# 能量重分配器 +# ============================================================================ + +class EnergyRedistributor: + """ + 能量重分配器 + + 当某个功能点被覆盖后,将其剩余能量重新分配给其他未覆盖功能点 + """ + + def __init__(self, allocations: Dict[str, EnergyAllocation]): + """ + Args: + allocations: 能量分配字典 + """ + self.allocations = allocations + + def redistribute(self, completed_fp: str) -> Dict[str, float]: + """ + 重分配已完成功能点的剩余能量 + + Args: + completed_fp: 已完成的功能点名称 + + Returns: + 重分配详情 {target_fp: gained_energy} + """ + if completed_fp not in self.allocations: + return {} + + completed_alloc = self.allocations[completed_fp] + + # 回收剩余能量 + recovered_energy = completed_alloc.remaining + + if recovered_energy <= 0: + logger.debug(f"No remaining energy to recover from {completed_fp}") + return {} + + # 找出活跃的未完成目标 + active_targets = [ + alloc for alloc in self.allocations.values() + if alloc.state == EnergyState.ACTIVE and alloc.function_point != completed_fp + ] + + if not active_targets: + logger.info(f"No active targets to redistribute energy to.") + return {} + + # 按重要性比例分配 + total_importance = sum(a.importance for a in active_targets) + redistribution = {} + + for alloc in active_targets: + if total_importance > 0: + gain = (alloc.importance / total_importance) * recovered_energy + else: + gain = recovered_energy / len(active_targets) + + alloc.allocated += gain + alloc.remaining += gain + redistribution[alloc.function_point] = gain + + # 清零已完成目标的剩余能量 + completed_alloc.remaining = 0 + + logger.info(f"Redistributed {recovered_energy:.1f} energy from {completed_fp} " + f"to {len(redistribution)} targets") + + return redistribution + + def redistribute_all(self) -> Dict[str, Dict[str, float]]: + """ + 重分配所有已完成/暂停目标的剩余能量 + + Returns: + 完整的重分配详情 + """ + all_redistributions = {} + + # 收集所有可回收能量 + completed_fps = [ + name for name, alloc in self.allocations.items() + if alloc.state in [EnergyState.COMPLETED, EnergyState.SUSPENDED] + and alloc.remaining > 0 + ] + + for fp in completed_fps: + redistribution = self.redistribute(fp) + if redistribution: + all_redistributions[fp] = redistribution + + return all_redistributions + + def revive_suspended(self, min_energy: float = 1.0) -> List[str]: + """ + 尝试复活暂停的目标(如果有足够的回收能量) + + Args: + min_energy: 复活所需的最小能量 + + Returns: + 复活的目标列表 + """ + revived = [] + + # 计算可用能量(来自已完成目标) + available_energy = sum( + alloc.remaining for alloc in self.allocations.values() + if alloc.state == EnergyState.COMPLETED and alloc.remaining > 0 + ) + + # 找出暂停的目标 + suspended = [ + alloc for alloc in self.allocations.values() + if alloc.state == EnergyState.SUSPENDED + ] + + for alloc in suspended: + if available_energy >= min_energy: + # 复活 + alloc.state = EnergyState.ACTIVE + alloc.remaining = min_energy + alloc.allocated += min_energy + alloc.consecutive_failures = 0 + available_energy -= min_energy + revived.append(alloc.function_point) + + logger.info(f"Revived suspended target: {alloc.function_point}") + + return revived + + +# ============================================================================ +# 能量分配器(主入口) +# ============================================================================ + +class EnergyAllocator: + """ + 能量分配器 - 第4层主入口 + + 整合所有子模块,提供统一的能量管理接口 + """ + + def __init__(self, + max_iterations: int = 5, + total_energy: float = None): + """ + Args: + max_iterations: 最大迭代次数 + total_energy: 总能量预算(默认使用 max_iterations) + """ + self.max_iterations = max_iterations + self.total_energy = total_energy or float(max_iterations) + + # 子模块 + self.initializer = EnergyInitializer(total_energy=self.total_energy) + self.allocations: Dict[str, EnergyAllocation] = {} + self.selector: Optional[TargetSelector] = None + self.tracker: Optional[EnergyConsumptionTracker] = None + self.redistributor: Optional[EnergyRedistributor] = None + + # 状态 + self.initialized = False + self.current_target: Optional[EnergyAllocation] = None + + def initialize(self, function_points: List[Dict]) -> Dict[str, Any]: + """ + 初始化能量分配 + + Args: + function_points: 功能点列表 + + Returns: + 初始化结果摘要 + """ + self.allocations = self.initializer.initialize( + function_points, + max_iterations=self.max_iterations + ) + + self.selector = TargetSelector(self.allocations) + self.tracker = EnergyConsumptionTracker(self.allocations) + self.redistributor = EnergyRedistributor(self.allocations) + self.initialized = True + + return { + 'total_energy': self.total_energy, + 'targets': len(self.allocations), + 'allocation_details': { + name: { + 'importance': alloc.importance, + 'allocated': alloc.allocated, + 'state': alloc.state.value + } + for name, alloc in self.allocations.items() + } + } + + def select_next_target(self) -> Optional[str]: + """ + 选择下一个生成目标 + + Returns: + 目标功能点名称,如果没有可用目标则返回 None + """ + if not self.initialized: + logger.warning("Energy allocator not initialized.") + return None + + self.current_target = self.selector.select_next_target() + return self.current_target.function_point if self.current_target else None + + def record_generation(self, + success: bool, + coverage_delta: float = 0.0, + energy_cost: float = 1.0, + quality_score: float = 0.0) -> Dict[str, Any]: + """ + 记录一次生成尝试 + + Args: + success: 是否成功覆盖目标 + coverage_delta: 覆盖率变化 + energy_cost: 消耗的能量 + quality_score: 代码质量分数 + + Returns: + 更新结果 + """ + if not self.current_target: + return {'status': 'error', 'message': 'No current target'} + + result = GenerationResult( + function_point=self.current_target.function_point, + success=success, + coverage_delta=coverage_delta, + energy_cost=energy_cost, + quality_score=quality_score + ) + + update_result = self.tracker.record_generation(result) + + # 如果成功,触发重分配 + if success: + self.redistributor.redistribute(self.current_target.function_point) + + return update_result + + def get_status(self) -> Dict[str, Any]: + """获取当前状态""" + if not self.initialized: + return {'initialized': False} + + active_count = sum(1 for a in self.allocations.values() + if a.state == EnergyState.ACTIVE and a.remaining > 0) + completed_count = sum(1 for a in self.allocations.values() + if a.state == EnergyState.COMPLETED) + + return { + 'initialized': True, + 'total_energy': self.total_energy, + 'total_targets': len(self.allocations), + 'active_targets': active_count, + 'completed_targets': completed_count, + 'current_target': self.current_target.function_point if self.current_target else None, + 'statistics': self.tracker.get_statistics() if self.tracker else None + } + + def get_target_context(self, target_name: str = None) -> str: + """ + 获取目标功能的上下文信息(用于 Prompt) + + Args: + target_name: 目标名称(默认使用当前目标) + + Returns: + 上下文字符串 + """ + if not target_name and self.current_target: + target_name = self.current_target.function_point + + if not target_name or target_name not in self.allocations: + return "" + + alloc = self.allocations[target_name] + + context = [] + context.append(f"[TARGET: {target_name}]") + context.append(f"Importance: {alloc.importance:.2f}") + context.append(f"Remaining Energy: {alloc.remaining:.1f} / {alloc.allocated:.1f}") + context.append(f"Previous Attempts: {alloc.total_attempts}") + + if alloc.consecutive_failures > 0: + context.append(f"Warning: {alloc.consecutive_failures} consecutive failures") + context.append("Consider a different approach or sequence") + + return "\n".join(context) + + def mark_targets_completed(self, function_names: List[str]) -> Dict[str, str]: + """ + 将已确认覆盖的功能点直接标记为完成。 + + 这用于基线同步或一次迭代中命中多个功能点的情况, + 避免仅依赖当前 target 的涨分信号来判断完成状态。 + """ + if not self.initialized: + return {} + + updates = {} + for name in function_names: + if name not in self.allocations: + continue + + alloc = self.allocations[name] + if alloc.state == EnergyState.COMPLETED: + updates[name] = "already_completed" + continue + + alloc.state = EnergyState.COMPLETED + alloc.consecutive_failures = 0 + alloc.remaining = 0.0 + updates[name] = "completed" + self.redistributor.redistribute(name) + + return updates + + def generate_report(self) -> str: + """生成能量分配报告""" + if not self.initialized: + return "Energy allocator not initialized." + + lines = [] + lines.append("=" * 60) + lines.append("ENERGY ALLOCATION REPORT") + lines.append("=" * 60) + lines.append(f"Total Energy: {self.total_energy:.1f}") + lines.append(f"Max Iterations: {self.max_iterations}") + lines.append("") + + lines.append("FUNCTION POINT STATUS:") + lines.append("-" * 60) + + for name, alloc in sorted(self.allocations.items(), + key=lambda x: x[1].importance, reverse=True): + status_icon = { + EnergyState.ACTIVE: "🔄", + EnergyState.COMPLETED: "✅", + EnergyState.DEPLETED: "❌", + EnergyState.SUSPENDED: "⏸️" + }.get(alloc.state, "❓") + + efficiency = (alloc.successful_attempts / alloc.total_attempts * 100 + if alloc.total_attempts > 0 else 0) + + lines.append(f"{status_icon} {name}") + lines.append(f" Importance: {alloc.importance:.2f} | " + f"Energy: {alloc.remaining:.1f}/{alloc.allocated:.1f} | " + f"Efficiency: {efficiency:.0f}%") + lines.append(f" Attempts: {alloc.total_attempts} | " + f"Consecutive Failures: {alloc.consecutive_failures}") + + lines.append("") + lines.append("SUMMARY:") + lines.append("-" * 60) + stats = self.tracker.get_statistics() + lines.append(f"Total Attempts: {stats['total_attempts']}") + lines.append(f"Successful: {stats['successful_attempts']}") + lines.append(f"Success Rate: {stats['success_rate']*100:.1f}%") + + completed = sum(1 for a in self.allocations.values() + if a.state == EnergyState.COMPLETED) + lines.append(f"Targets Covered: {completed} / {len(self.allocations)}") + + lines.append("=" * 60) + + return "\n".join(lines) + + +# ============================================================================ +# 便捷函数 +# ============================================================================ + +def create_energy_allocator(function_points: List[Dict], + max_iterations: int = 5) -> EnergyAllocator: + """ + 便捷函数:创建并初始化能量分配器 + + Args: + function_points: 功能点列表 + max_iterations: 最大迭代次数 + + Returns: + 初始化完成的能量分配器 + """ + allocator = EnergyAllocator(max_iterations=max_iterations) + allocator.initialize(function_points) + return allocator diff --git a/autoline/quality_evaluator.py b/autoline/quality_evaluator.py new file mode 100644 index 0000000..bd28191 --- /dev/null +++ b/autoline/quality_evaluator.py @@ -0,0 +1,1039 @@ +""" +Description : Quality Evaluation Layer (Layer 3) + - Diversity Evaluator (4-dimension) + - Semantic Coverage Calculator + - Test Case Quality Scorer +Author : CGA Enhancement Project +Time : 2026/03/16 +""" + +import re +import logging +import hashlib +import json +from typing import List, Dict, Optional, Any, Tuple, Set +from dataclasses import dataclass, field +from enum import Enum +from collections import defaultdict + +logger = logging.getLogger(__name__) + + +# ============================================================================ +# 配置常量 +# ============================================================================ + +class QualityConfig: + """质量评估配置""" + + # 多样性评估权重 + SEQUENCE_DIVERSITY_WEIGHT = 0.30 # 输入序列多样性权重 + SEMANTIC_VECTOR_WEIGHT = 0.25 # 语义向量多样性权重 + EXECUTION_PATH_WEIGHT = 0.25 # 执行路径多样性权重 + FUNCTION_COVERAGE_WEIGHT = 0.20 # 功能覆盖多样性权重 + + # 多样性阈值 + MIN_DIVERSITY_THRESHOLD = 0.1 # 最低多样性阈值 + HIGH_DIVERSITY_THRESHOLD = 0.7 # 高多样性阈值 + + # 语义覆盖率阈值 + SEMANTIC_COVERAGE_TARGET = 0.85 # 目标语义覆盖率 + + # 编辑距离配置 + MIN_EDIT_DISTANCE = 3 # 最小编辑距离要求 + + # 相似度阈值 + SIMILARITY_THRESHOLD = 0.8 # 相似度阈值(超过此值认为相似) + + +# ============================================================================ +# 数据结构定义 +# ============================================================================ + +class DiversityDimension(Enum): + """多样性维度枚举""" + SEQUENCE = "sequence" # 输入序列多样性 + SEMANTIC_VECTOR = "semantic" # 语义向量多样性 + EXECUTION_PATH = "path" # 执行路径多样性 + FUNCTION_COVERAGE = "function" # 功能覆盖多样性 + + +@dataclass +class DiversityScore: + """ + 多样性评分 + + Attributes: + sequence_diversity: 输入序列多样性得分 + semantic_diversity: 语义向量多样性得分 + path_diversity: 执行路径多样性得分 + function_diversity: 功能覆盖多样性得分 + overall_score: 综合多样性得分 + details: 详细信息 + """ + sequence_diversity: float = 0.0 + semantic_diversity: float = 0.0 + path_diversity: float = 0.0 + function_diversity: float = 0.0 + overall_score: float = 0.0 + details: Dict[str, Any] = field(default_factory=dict) + + def calculate_overall(self) -> float: + """计算综合多样性得分""" + self.overall_score = ( + QualityConfig.SEQUENCE_DIVERSITY_WEIGHT * self.sequence_diversity + + QualityConfig.SEMANTIC_VECTOR_WEIGHT * self.semantic_diversity + + QualityConfig.EXECUTION_PATH_WEIGHT * self.path_diversity + + QualityConfig.FUNCTION_COVERAGE_WEIGHT * self.function_diversity + ) + return self.overall_score + + def to_dict(self) -> Dict[str, Any]: + """转换为字典""" + return { + 'sequence_diversity': round(self.sequence_diversity, 4), + 'semantic_diversity': round(self.semantic_diversity, 4), + 'path_diversity': round(self.path_diversity, 4), + 'function_diversity': round(self.function_diversity, 4), + 'overall_score': round(self.overall_score, 4), + 'details': self.details + } + + +@dataclass +class SemanticCoverageResult: + """ + 语义覆盖率结果 + + Attributes: + total_function_points: 总功能点数 + covered_function_points: 已覆盖功能点数 + semantic_coverage: 语义覆盖率 + coverage_by_type: 按类型统计的覆盖率 + uncovered_important: 未覆盖的高重要性功能点 + """ + total_function_points: int = 0 + covered_function_points: int = 0 + semantic_coverage: float = 0.0 + coverage_by_type: Dict[str, float] = field(default_factory=dict) + uncovered_important: List[Dict[str, Any]] = field(default_factory=list) + + def to_dict(self) -> Dict[str, Any]: + """转换为字典""" + return { + 'total_function_points': self.total_function_points, + 'covered_function_points': self.covered_function_points, + 'semantic_coverage': round(self.semantic_coverage, 4), + 'coverage_by_type': self.coverage_by_type, + 'uncovered_important': self.uncovered_important + } + + +@dataclass +class FunctionCoverageState: + """ + 功能点覆盖状态 + + Attributes: + name: 功能点名称 + fp_type: 功能点类型 + importance: 重要性评分 + covered: 是否已覆盖 + covered_by: 覆盖该功能点的测试用例ID列表 + last_check_iteration: 最后检查的迭代次数 + """ + name: str + fp_type: str + importance: float + covered: bool = False + covered_by: List[str] = field(default_factory=list) + last_check_iteration: int = 0 + + +# ============================================================================ +# 编辑距离计算器 +# ============================================================================ + +class EditDistanceCalculator: + """编辑距离计算器""" + + @staticmethod + def levenshtein_distance(s1: str, s2: str) -> int: + """ + 计算Levenshtein编辑距离 + + Args: + s1: 字符串1 + s2: 字符串2 + + Returns: + 编辑距离 + """ + if len(s1) < len(s2): + return EditDistanceCalculator.levenshtein_distance(s2, s1) + + if len(s2) == 0: + return len(s1) + + previous_row = range(len(s2) + 1) + for i, c1 in enumerate(s1): + current_row = [i + 1] + for j, c2 in enumerate(s2): + # 插入、删除、替换 + insertions = previous_row[j + 1] + 1 + deletions = current_row[j] + 1 + substitutions = previous_row[j] + (c1 != c2) + current_row.append(min(insertions, deletions, substitutions)) + previous_row = current_row + + return previous_row[-1] + + @staticmethod + def normalized_distance(s1: str, s2: str) -> float: + """ + 计算归一化的编辑距离 (0-1范围) + + Args: + s1: 字符串1 + s2: 字符串2 + + Returns: + 归一化编辑距离 (0表示相同,1表示完全不同) + """ + if not s1 and not s2: + return 0.0 + max_len = max(len(s1), len(s2)) + if max_len == 0: + return 0.0 + distance = EditDistanceCalculator.levenshtein_distance(s1, s2) + return distance / max_len + + @staticmethod + def similarity(s1: str, s2: str) -> float: + """ + 计算相似度 (1 - 归一化编辑距离) + + Args: + s1: 字符串1 + s2: 字符串2 + + Returns: + 相似度 (1表示相同,0表示完全不同) + """ + return 1.0 - EditDistanceCalculator.normalized_distance(s1, s2) + + +# ============================================================================ +# 序列特征提取器 +# ============================================================================ + +class SequenceFeatureExtractor: + """序列特征提取器""" + + @staticmethod + def extract_features(code: str) -> Dict[str, Any]: + """ + 从代码中提取序列特征 + + Args: + code: 测试代码 + + Returns: + 特征字典 + """ + features = { + 'signal_assignments': [], # 信号赋值序列 + 'delay_patterns': [], # 延时模式 + 'control_structures': [], # 控制结构 + 'signal_values': {}, # 信号值映射 + 'operation_sequence': [], # 操作序列 + 'code_hash': '' # 代码哈希 + } + + # 提取信号赋值 + assign_pattern = r'(\w+)\s*=\s*([^;]+);' + for match in re.finditer(assign_pattern, code): + signal = match.group(1) + value = match.group(2).strip() + features['signal_assignments'].append((signal, value)) + if signal not in features['signal_values']: + features['signal_values'][signal] = [] + features['signal_values'][signal].append(value) + + # 提取延时模式 + delay_pattern = r'#(\d+)' + delays = [int(d) for d in re.findall(delay_pattern, code)] + features['delay_patterns'] = delays + + # 提取repeat循环 + repeat_pattern = r'repeat\s*\(\s*(\d+)\s*\)' + repeats = [int(r) for r in re.findall(repeat_pattern, code)] + features['control_structures'].extend([('repeat', r) for r in repeats]) + + # 提取posedge/negedge + edge_pattern = r'@(?:posedge|negedge)\s+(\w+)' + edges = re.findall(edge_pattern, code) + features['control_structures'].extend([('edge', e) for e in edges]) + + # 构建操作序列 + all_operations = [] + + # 按位置排序的所有操作 + for match in re.finditer(r'((?:\w+\s*=\s*[^;]+;)|(?:#\d+;)|(?:repeat\s*\(\d+\)[^;]+;)|(?:@\([^)]+\)))', code): + all_operations.append(match.group(1).strip()) + + features['operation_sequence'] = all_operations + + # 计算代码哈希 + normalized_code = re.sub(r'\s+', ' ', code).strip() + features['code_hash'] = hashlib.md5(normalized_code.encode()).hexdigest()[:16] + + return features + + @staticmethod + def extract_signal_sequence(code: str, signals: List[str] = None) -> List[Tuple[str, str, str]]: + """ + 提取信号赋值序列 + + Args: + code: 测试代码 + signals: 关注的信号列表(可选) + + Returns: + (信号名, 值, 上下文) 元组列表 + """ + sequences = [] + + # 按行处理,保持上下文 + lines = code.split('\n') + context = "" + + for line in lines: + stripped = line.strip() + + # 更新上下文 + if stripped.startswith('//'): + context = stripped + continue + + # 提取赋值 + assign_match = re.match(r'(\w+)\s*=\s*([^;]+);', stripped) + if assign_match: + signal = assign_match.group(1) + value = assign_match.group(2).strip() + + # 如果指定了信号列表,只提取关注的信号 + if signals is None or signal in signals: + sequences.append((signal, value, context)) + + return sequences + + +# ============================================================================ +# 多样性评估器 +# ============================================================================ + +class DiversityEvaluator: + """ + 多样性评估器 + + 四维度多样性评估: + 1. 输入序列多样性:计算与已有序列的编辑距离 + 2. 语义向量多样性:计算代码特征向量的相似度 + 3. 执行路径多样性:计算新覆盖代码行的比例 + 4. 功能覆盖多样性:计算新覆盖功能点的比例 + """ + + def __init__(self): + """初始化多样性评估器""" + self.history: List[Dict[str, Any]] = [] + self.feature_extractor = SequenceFeatureExtractor() + self.edit_distance_calc = EditDistanceCalculator() + + def add_to_history(self, + code: str, + covered_lines: Set[int] = None, + covered_functions: List[str] = None, + test_id: str = ""): + """ + 添加测试用例到历史记录 + + Args: + code: 测试代码 + covered_lines: 覆盖的代码行 + covered_functions: 覆盖的功能点列表 + test_id: 测试用例ID + """ + features = self.feature_extractor.extract_features(code) + + record = { + 'test_id': test_id, + 'code': code, + 'features': features, + 'covered_lines': covered_lines or set(), + 'covered_functions': covered_functions or [], + 'code_hash': features['code_hash'] + } + + self.history.append(record) + + def evaluate(self, + new_code: str, + new_covered_lines: Set[int] = None, + new_covered_functions: List[str] = None) -> DiversityScore: + """ + 评估新测试用例的多样性 + + Args: + new_code: 新测试代码 + new_covered_lines: 新覆盖的代码行 + new_covered_functions: 新覆盖的功能点列表 + + Returns: + 多样性评分 + """ + score = DiversityScore() + + if not self.history: + # 第一个测试用例,给予最高多样性 + score.sequence_diversity = 1.0 + score.semantic_diversity = 1.0 + score.path_diversity = 1.0 + score.function_diversity = 1.0 + score.calculate_overall() + score.details = {'reason': 'first_test_case'} + return score + + # 提取新代码特征 + new_features = self.feature_extractor.extract_features(new_code) + new_covered_lines = new_covered_lines or set() + new_covered_functions = new_covered_functions or [] + + # 1. 计算输入序列多样性 + score.sequence_diversity = self._evaluate_sequence_diversity(new_code, new_features) + + # 2. 计算语义向量多样性 + score.semantic_diversity = self._evaluate_semantic_diversity(new_features) + + # 3. 计算执行路径多样性 + score.path_diversity = self._evaluate_path_diversity(new_covered_lines) + + # 4. 计算功能覆盖多样性 + score.function_diversity = self._evaluate_function_diversity(new_covered_functions) + + # 计算综合得分 + score.calculate_overall() + + # 添加详细信息 + score.details = { + 'history_size': len(self.history), + 'new_code_hash': new_features['code_hash'], + 'signal_count': len(new_features['signal_values']), + 'operation_count': len(new_features['operation_sequence']) + } + + return score + + def _evaluate_sequence_diversity(self, new_code: str, new_features: Dict) -> float: + """评估输入序列多样性""" + if not self.history: + return 1.0 + + # 提取操作序列 + new_ops = new_features['operation_sequence'] + + # 计算与历史中所有测试的最小相似度(即最大差异) + max_distance = 0.0 + + for record in self.history[-10:]: # 只比较最近10个 + hist_ops = record['features']['operation_sequence'] + + # 将操作序列转换为字符串计算编辑距离 + new_ops_str = ' '.join(new_ops) + hist_ops_str = ' '.join(hist_ops) + + similarity = self.edit_distance_calc.similarity(new_ops_str, hist_ops_str) + distance = 1.0 - similarity + max_distance = max(max_distance, distance) + + return max_distance + + def _evaluate_semantic_diversity(self, new_features: Dict) -> float: + """评估语义向量多样性""" + if not self.history: + return 1.0 + + # 构建特征向量 + new_vector = self._build_feature_vector(new_features) + + # 计算与历史特征向量的最小相似度 + max_distance = 0.0 + + for record in self.history[-10:]: + hist_vector = self._build_feature_vector(record['features']) + similarity = self._cosine_similarity(new_vector, hist_vector) + distance = 1.0 - similarity + max_distance = max(max_distance, distance) + + return max_distance + + def _evaluate_path_diversity(self, new_covered_lines: Set[int]) -> float: + """评估执行路径多样性""" + if not self.history: + return 1.0 + + if not new_covered_lines: + return 0.0 + + # 计算历史覆盖的并集 + all_covered = set() + for record in self.history: + all_covered.update(record['covered_lines']) + + # 计算新覆盖的行 + new_lines = new_covered_lines - all_covered + + if not new_covered_lines: + return 0.0 + + # 新覆盖比例 + novelty_ratio = len(new_lines) / len(new_covered_lines) + return novelty_ratio + + def _evaluate_function_diversity(self, new_covered_functions: List[str]) -> float: + """评估功能覆盖多样性""" + if not self.history: + return 1.0 + + if not new_covered_functions: + return 0.0 + + # 计算历史覆盖的功能点并集 + all_covered = set() + for record in self.history: + all_covered.update(record['covered_functions']) + + # 计算新覆盖的功能点 + new_functions = set(new_covered_functions) - all_covered + + if not new_covered_functions: + return 0.0 + + # 新覆盖比例 + novelty_ratio = len(new_functions) / len(new_covered_functions) + return novelty_ratio + + def _build_feature_vector(self, features: Dict) -> Dict[str, float]: + """构建特征向量""" + vector = {} + + # 信号数量 + vector['signal_count'] = len(features['signal_values']) + + # 操作数量 + vector['operation_count'] = len(features['operation_sequence']) + + # 延时数量 + vector['delay_count'] = len(features['delay_patterns']) + + # 延时总和 + vector['delay_sum'] = sum(features['delay_patterns']) + + # 控制结构数量 + vector['control_count'] = len(features['control_structures']) + + # repeat循环数量 + repeat_count = sum(1 for c in features['control_structures'] if c[0] == 'repeat') + vector['repeat_count'] = repeat_count + + # 边沿等待数量 + edge_count = sum(1 for c in features['control_structures'] if c[0] == 'edge') + vector['edge_count'] = edge_count + + # 赋值数量 + vector['assignment_count'] = len(features['signal_assignments']) + + return vector + + def _cosine_similarity(self, v1: Dict[str, float], v2: Dict[str, float]) -> float: + """计算余弦相似度""" + # 获取所有键 + all_keys = set(v1.keys()) | set(v2.keys()) + + if not all_keys: + return 0.0 + + # 计算点积和模 + dot_product = 0.0 + norm1 = 0.0 + norm2 = 0.0 + + for key in all_keys: + val1 = v1.get(key, 0.0) + val2 = v2.get(key, 0.0) + dot_product += val1 * val2 + norm1 += val1 * val1 + norm2 += val2 * val2 + + if norm1 == 0 or norm2 == 0: + return 0.0 + + return dot_product / (norm1 ** 0.5 * norm2 ** 0.5) + + def get_diversity_statistics(self) -> Dict[str, Any]: + """获取多样性统计信息""" + # if not self.history: + # return { + # 'total_tests': 0, + # 'unique_hashes': 0, + # 'avg_diversity': 0.0 + # } + if not self.history: + return { + 'total_tests': 0, + 'unique_hashes': 0, + 'avg_diversity': 0.0, + 'duplicate_ratio': 0.0 # ✅ 添加缺失的键 + } + + unique_hashes = len(set(r['code_hash'] for r in self.history)) + + return { + 'total_tests': len(self.history), + 'unique_hashes': unique_hashes, + 'duplicate_ratio': 1.0 - (unique_hashes / len(self.history)) if self.history else 0.0 + } + + +# ============================================================================ +# 语义覆盖率计算器 +# ============================================================================ + +class SemanticCoverageCalculator: + """ + 语义覆盖率计算器 + + 计算基于功能点重要性的语义覆盖率: + 语义覆盖率 = Σ(已覆盖功能点重要性) / Σ(所有功能点重要性) + """ + + def __init__(self, function_points: List[Dict[str, Any]] = None): + """ + 初始化语义覆盖率计算器 + + Args: + function_points: 功能点列表 + """ + self.function_points: Dict[str, FunctionCoverageState] = {} + self.total_importance = 0.0 + self.covered_importance = 0.0 + + if function_points: + self.initialize(function_points) + + def initialize(self, function_points: List[Dict[str, Any]]) -> Dict[str, Any]: + """ + 初始化功能点覆盖状态 + + Args: + function_points: 功能点列表 + + Returns: + 初始化结果 + """ + self.function_points.clear() + self.total_importance = 0.0 + self.covered_importance = 0.0 + + for fp in function_points: + name = fp.get('name', '') + fp_type = fp.get('type', 'unknown') + importance = fp.get('importance', 0.0) + + self.function_points[name] = FunctionCoverageState( + name=name, + fp_type=fp_type, + importance=importance, + covered=False, + covered_by=[], + last_check_iteration=0 + ) + + self.total_importance += importance + + return { + 'total_function_points': len(self.function_points), + 'total_importance': self.total_importance + } + + def update_coverage(self, + covered_lines: Set[int] = None, + covered_functions: List[str] = None, + test_id: str = "", + iteration: int = 0) -> SemanticCoverageResult: + """ + 更新功能点覆盖状态 + + Args: + covered_lines: 覆盖的代码行 + covered_functions: 覆盖的功能点名称列表 + test_id: 测试用例ID + iteration: 当前迭代次数 + + Returns: + 语义覆盖率结果 + """ + # 更新覆盖状态 + if covered_functions: + for func_name in covered_functions: + if func_name in self.function_points: + fp = self.function_points[func_name] + if not fp.covered: + fp.covered = True + fp.covered_by.append(test_id) + fp.last_check_iteration = iteration + self.covered_importance += fp.importance + + return self.calculate_coverage() + + def calculate_coverage(self) -> SemanticCoverageResult: + """ + 计算当前语义覆盖率 + + Returns: + 语义覆盖率结果 + """ + result = SemanticCoverageResult() + + result.total_function_points = len(self.function_points) + result.covered_function_points = sum(1 for fp in self.function_points.values() if fp.covered) + + # 计算语义覆盖率 + if self.total_importance > 0: + result.semantic_coverage = self.covered_importance / self.total_importance + + # 按类型统计 + type_stats = defaultdict(lambda: {'total': 0, 'covered': 0, 'importance': 0.0, 'covered_importance': 0.0}) + + for fp in self.function_points.values(): + type_stats[fp.fp_type]['total'] += 1 + type_stats[fp.fp_type]['importance'] += fp.importance + if fp.covered: + type_stats[fp.fp_type]['covered'] += 1 + type_stats[fp.fp_type]['covered_importance'] += fp.importance + + for fp_type, stats in type_stats.items(): + if stats['importance'] > 0: + result.coverage_by_type[fp_type] = stats['covered_importance'] / stats['importance'] + else: + result.coverage_by_type[fp_type] = 0.0 + + # 找出未覆盖的高重要性功能点 + uncovered_important = [ + { + 'name': fp.name, + 'type': fp.fp_type, + 'importance': fp.importance + } + for fp in self.function_points.values() + if not fp.covered and fp.importance >= 0.5 + ] + uncovered_important.sort(key=lambda x: x['importance'], reverse=True) + result.uncovered_important = uncovered_important[:5] # 只返回前5个 + + return result + + def get_function_point_status(self, name: str) -> Optional[FunctionCoverageState]: + """获取指定功能点的覆盖状态""" + return self.function_points.get(name) + + def get_uncovered_function_points(self, + min_importance: float = 0.0, + fp_type: str = None) -> List[FunctionCoverageState]: + """ + 获取未覆盖的功能点 + + Args: + min_importance: 最小重要性阈值 + fp_type: 功能点类型过滤(可选) + + Returns: + 未覆盖的功能点列表 + """ + uncovered = [] + + for fp in self.function_points.values(): + if fp.covered: + continue + if fp.importance < min_importance: + continue + if fp_type and fp.fp_type != fp_type: + continue + uncovered.append(fp) + + # 按重要性排序 + uncovered.sort(key=lambda x: x.importance, reverse=True) + return uncovered + + def get_coverage_report(self) -> str: + """生成覆盖率报告""" + result = self.calculate_coverage() + + lines = [] + lines.append("=" * 60) + lines.append("SEMANTIC COVERAGE REPORT") + lines.append("=" * 60) + lines.append("") + lines.append(f"Total Function Points: {result.total_function_points}") + lines.append(f"Covered Function Points: {result.covered_function_points}") + lines.append(f"Semantic Coverage: {result.semantic_coverage:.2%}") + lines.append("") + lines.append("Coverage by Type:") + for fp_type, coverage in result.coverage_by_type.items(): + lines.append(f" - {fp_type}: {coverage:.2%}") + lines.append("") + + if result.uncovered_important: + lines.append("Uncovered High-Importance Function Points:") + for fp in result.uncovered_important: + lines.append(f" - {fp['name']} ({fp['type']}): importance={fp['importance']:.2f}") + + lines.append("") + lines.append("=" * 60) + + return "\n".join(lines) + + def to_dict(self) -> Dict[str, Any]: + """转换为字典""" + return { + 'total_importance': self.total_importance, + 'covered_importance': self.covered_importance, + 'function_points': { + name: { + 'type': fp.fp_type, + 'importance': fp.importance, + 'covered': fp.covered, + 'covered_by': fp.covered_by + } + for name, fp in self.function_points.items() + } + } + + +# ============================================================================ +# 质量评估器(主入口) +# ============================================================================ + +class QualityEvaluator: + """ + 质量评估器 - 第3层主入口 + + 整合多样性评估和语义覆盖率计算,提供统一的质量评估接口 + """ + + def __init__(self, function_points: List[Dict[str, Any]] = None): + """ + 初始化质量评估器 + + Args: + function_points: 功能点列表 + """ + self.diversity_evaluator = DiversityEvaluator() + self.semantic_coverage = SemanticCoverageCalculator(function_points) + self.evaluation_history: List[Dict[str, Any]] = [] + + def initialize(self, function_points: List[Dict[str, Any]]) -> Dict[str, Any]: + """ + 初始化质量评估器 + + Args: + function_points: 功能点列表 + + Returns: + 初始化结果 + """ + result = self.semantic_coverage.initialize(function_points) + self.evaluation_history.clear() + return result + + def evaluate_test_case(self, + code: str, + covered_lines: Set[int] = None, + covered_functions: List[str] = None, + test_id: str = "", + iteration: int = 0) -> Dict[str, Any]: + """ + 评估测试用例质量 + + Args: + code: 测试代码 + covered_lines: 覆盖的代码行 + covered_functions: 覆盖的功能点列表 + test_id: 测试用例ID + iteration: 当前迭代次数 + + Returns: + 评估结果字典 + """ + # 1. 多样性评估 + diversity_score = self.diversity_evaluator.evaluate( + new_code=code, + new_covered_lines=covered_lines, + new_covered_functions=covered_functions + ) + + # 2. 更新语义覆盖率 + coverage_result = self.semantic_coverage.update_coverage( + covered_lines=covered_lines, + covered_functions=covered_functions, + test_id=test_id, + iteration=iteration + ) + + # 3. 计算质量得分 + quality_score = self._calculate_quality_score(diversity_score, coverage_result) + + # 4. 记录评估历史 + evaluation_result = { + 'test_id': test_id, + 'iteration': iteration, + 'diversity': diversity_score.to_dict(), + 'coverage': coverage_result.to_dict(), + 'quality_score': quality_score + } + self.evaluation_history.append(evaluation_result) + + # 5. 添加到多样性历史 + self.diversity_evaluator.add_to_history( + code=code, + covered_lines=covered_lines, + covered_functions=covered_functions, + test_id=test_id + ) + + return evaluation_result + + def _calculate_quality_score(self, + diversity: DiversityScore, + coverage: SemanticCoverageResult) -> float: + """ + 计算综合质量得分 + + Args: + diversity: 多样性评分 + coverage: 语义覆盖率结果 + + Returns: + 质量得分 (0-1) + """ + # 多样性权重 + diversity_weight = 0.4 + + # 覆盖率增量权重 + coverage_weight = 0.6 + + # 综合得分 + score = ( + diversity_weight * diversity.overall_score + + coverage_weight * coverage.semantic_coverage + ) + + return min(1.0, max(0.0, score)) + + def should_accept(self, + evaluation_result: Dict[str, Any], + min_diversity: float = QualityConfig.MIN_DIVERSITY_THRESHOLD) -> Tuple[bool, str]: + """ + 判断是否应该接受该测试用例 + + Args: + evaluation_result: 评估结果 + min_diversity: 最低多样性阈值 + + Returns: + (是否接受, 原因) + """ + diversity = evaluation_result.get('diversity', {}) + overall_diversity = diversity.get('overall_score', 0.0) + + if overall_diversity < min_diversity: + return False, f"Diversity too low: {overall_diversity:.2f} < {min_diversity}" + + return True, "Passed quality check" + + def get_statistics(self) -> Dict[str, Any]: + """获取统计信息""" + diversity_stats = self.diversity_evaluator.get_diversity_statistics() + coverage_result = self.semantic_coverage.calculate_coverage() + + return { + 'diversity': diversity_stats, + 'coverage': coverage_result.to_dict(), + 'total_evaluations': len(self.evaluation_history) + } + + def generate_report(self) -> str: + """生成完整报告""" + lines = [] + lines.append("=" * 70) + lines.append("QUALITY EVALUATION REPORT - LAYER 3") + lines.append("=" * 70) + lines.append("") + + # 多样性统计 + stats = self.get_statistics() + lines.append("[DIVERSITY STATISTICS]") + lines.append(f"Total Tests: {stats['diversity']['total_tests']}") + lines.append(f"Unique Tests: {stats['diversity']['unique_hashes']}") + lines.append(f"Duplicate Ratio: {stats['diversity']['duplicate_ratio']:.2%}") + lines.append("") + + # 语义覆盖率 + lines.append("[SEMANTIC COVERAGE]") + lines.append(self.semantic_coverage.get_coverage_report()) + lines.append("") + + # 质量趋势 + if self.evaluation_history: + lines.append("[QUALITY TREND]") + avg_quality = sum(e['quality_score'] for e in self.evaluation_history) / len(self.evaluation_history) + avg_diversity = sum(e['diversity']['overall_score'] for e in self.evaluation_history) / len(self.evaluation_history) + lines.append(f"Average Quality Score: {avg_quality:.2f}") + lines.append(f"Average Diversity Score: {avg_diversity:.2f}") + lines.append("") + + lines.append("=" * 70) + + return "\n".join(lines) + + +# ============================================================================ +# 便捷函数 +# ============================================================================ + +def create_quality_evaluator(function_points: List[Dict[str, Any]] = None) -> QualityEvaluator: + """ + 创建质量评估器 + + Args: + function_points: 功能点列表 + + Returns: + 初始化完成的质量评估器 + """ + return QualityEvaluator(function_points=function_points) + + +def evaluate_diversity(code1: str, code2: str) -> float: + """ + 快速评估两个代码的多样性 + + Args: + code1: 代码1 + code2: 代码2 + + Returns: + 多样性得分 (0-1, 1表示完全不同) + """ + calc = EditDistanceCalculator() + return calc.normalized_distance(code1, code2) \ No newline at end of file diff --git a/autoline/semantic_analyzer.py b/autoline/semantic_analyzer.py new file mode 100644 index 0000000..f69ff45 --- /dev/null +++ b/autoline/semantic_analyzer.py @@ -0,0 +1,1029 @@ +""" +Description : Semantic Analysis Layer (Layer 0) + - RTL Structure Parser + - Function Point Recognizer + - Importance Scorer + - Test Scenario Generator +Author : CGA Enhancement Project +Time : 2026/03/08 +""" + +import re +import logging +from typing import List, Dict, Tuple, Optional, Any +from dataclasses import dataclass, field +from enum import Enum + +logger = logging.getLogger(__name__) + + +# ============================================================================ +# 数据结构定义 +# ============================================================================ + +class FunctionPointType(Enum): + """功能点类型枚举""" + FSM = "fsm" # 状态机 + COUNTER = "counter" # 计数器 + CONDITION = "condition" # 条件分支 + PROTOCOL = "protocol" # 协议接口 + EXCEPTION = "exception" # 异常处理 + + +@dataclass +class Port: + """端口信息""" + name: str + direction: str # input, output, inout + width: int = 1 + msb: int = 0 + lsb: int = 0 + + +@dataclass +class Signal: + """信号信息""" + name: str + signal_type: str # wire, reg, logic + width: int = 1 + msb: int = 0 + lsb: int = 0 + + +@dataclass +class Parameter: + """参数信息""" + name: str + value: str + param_type: str = "parameter" # parameter, localparam + + +@dataclass +class CodeRegion: + """代码区域""" + start_line: int + end_line: int + region_type: str # always, assign, instance, etc. + content: str + + +@dataclass +class FunctionPoint: + """功能点""" + name: str + fp_type: FunctionPointType + location: Tuple[int, int] # (start_line, end_line) + code_region: str + importance_score: float = 0.0 + covered: bool = False + attributes: Dict[str, Any] = field(default_factory=dict) + + +@dataclass +class TestScenario: + """测试场景建议""" + name: str + target_function: str + description: str + priority: float + scenario_type: str # transition, boundary, exception, etc. + + +@dataclass +class RTLStructure: + """RTL结构信息""" + module_name: str = "" + ports: List[Port] = field(default_factory=list) + signals: List[Signal] = field(default_factory=list) + parameters: List[Parameter] = field(default_factory=list) + code_regions: List[CodeRegion] = field(default_factory=list) + signal_dependencies: Dict[str, List[str]] = field(default_factory=dict) + + +# ============================================================================ +# RTL 结构解析器 +# ============================================================================ + +class RTLStructureParser: + """ + RTL结构解析器 + 解析RTL代码的基础结构信息,为后续分析提供结构化数据 + """ + + def __init__(self, rtl_code: str): + """ + Args: + rtl_code: RTL源代码字符串 + """ + self.code = rtl_code + self.lines = rtl_code.split('\n') + self.structure = RTLStructure() + + def parse(self) -> RTLStructure: + """执行完整解析""" + self._parse_module_header() + self._parse_ports() + self._parse_signals() + self._parse_parameters() + self._parse_code_regions() + self._build_signal_dependencies() + return self.structure + + def _parse_module_header(self): + """解析模块头""" + # 匹配 module name (...) 或 module name; + pattern = r'module\s+(\w+)\s*(?:#\s*\([^)]*\))?\s*(?:\(([^)]*)\))?' + match = re.search(pattern, self.code, re.DOTALL) + if match: + self.structure.module_name = match.group(1) + + def _parse_ports(self): + """解析端口定义""" + # 从模块声明中提取端口 + port_patterns = [ + r'(input|output|inout)\s+(?:wire\s+|reg\s+)?(?:\[(\d+):(\d+)\]\s+)?(\w+)', + r'(input|output|inout)\s+(?:\[(\d+):(\d+)\]\s+)?(\w+)', + ] + + for pattern in port_patterns: + for match in re.finditer(pattern, self.code): + direction = match.group(1) + msb = int(match.group(2)) if match.group(2) else 0 + lsb = int(match.group(3)) if match.group(3) else 0 + name = match.group(4) + width = abs(msb - lsb) + 1 + + # 避免重复添加 + if not any(p.name == name for p in self.structure.ports): + self.structure.ports.append(Port( + name=name, + direction=direction, + width=width, + msb=msb, + lsb=lsb + )) + + def _parse_signals(self): + """解析内部信号定义""" + patterns = [ + r'(wire|reg|logic)\s+(?:\[(\d+):(\d+)\]\s+)?(\w+)\s*;', + r'(wire|reg|logic)\s+\[(\d+):(\d+)\]\s+(\w+)\s*;', + ] + + for pattern in patterns: + for match in re.finditer(pattern, self.code): + signal_type = match.group(1) + msb = int(match.group(2)) if match.group(2) else 0 + lsb = int(match.group(3)) if match.group(3) else 0 + name = match.group(4) + width = abs(msb - lsb) + 1 + + if not any(s.name == name for s in self.structure.signals): + self.structure.signals.append(Signal( + name=name, + signal_type=signal_type, + width=width, + msb=msb, + lsb=lsb + )) + + def _parse_parameters(self): + """解析参数定义""" + # parameter 定义 + param_pattern = r'parameter\s+(?:\[(\d+):(\d+)\]\s+)?(\w+)\s*=\s*([^;]+)' + for match in re.finditer(param_pattern, self.code): + name = match.group(3) + value = match.group(4).strip() + self.structure.parameters.append(Parameter( + name=name, + value=value, + param_type="parameter" + )) + + # localparam 定义 + localparam_pattern = r'localparam\s+(?:\[(\d+):(\d+)\]\s+)?(\w+)\s*=\s*([^;]+)' + for match in re.finditer(localparam_pattern, self.code): + name = match.group(3) + value = match.group(4).strip() + self.structure.parameters.append(Parameter( + name=name, + value=value, + param_type="localparam" + )) + + def _parse_code_regions(self): + """解析代码区域""" + # always 块 + always_pattern = r'(always\s*@\s*\([^)]*\)\s*begin.*?end)' + for i, match in enumerate(re.finditer(always_pattern, self.code, re.DOTALL)): + start = self.code[:match.start()].count('\n') + 1 + end = self.code[:match.end()].count('\n') + 1 + self.structure.code_regions.append(CodeRegion( + start_line=start, + end_line=end, + region_type="always", + content=match.group(1) + )) + + # assign 语句 + assign_pattern = r'(assign\s+\w+\s*=\s*[^;]+;)' + for i, match in enumerate(re.finditer(assign_pattern, self.code)): + start = self.code[:match.start()].count('\n') + 1 + end = start + self.structure.code_regions.append(CodeRegion( + start_line=start, + end_line=end, + region_type="assign", + content=match.group(1) + )) + + def _build_signal_dependencies(self): + """构建信号依赖图""" + # 分析 assign 语句中的依赖 + assign_pattern = r'assign\s+(\w+)\s*=\s*([^;]+);' + for match in re.finditer(assign_pattern, self.code): + target = match.group(1) + source = match.group(2) + # 提取源表达式中的信号名 + deps = re.findall(r'\b([a-zA-Z_][a-zA-Z0-9_]*)\b', source) + self.structure.signal_dependencies[target] = deps + + # 分析 always 块中的依赖 + always_pattern = r'always\s*@\s*\([^)]*\)\s*begin(.*?)end' + for match in re.finditer(always_pattern, self.code, re.DOTALL): + block = match.group(1) + # 查找赋值语句 + assigns = re.findall(r'(\w+)\s*(?:<=|=)\s*([^;]+);', block) + for target, source in assigns: + deps = re.findall(r'\b([a-zA-Z_][a-zA-Z0-9_]*)\b', source) + if target not in self.structure.signal_dependencies: + self.structure.signal_dependencies[target] = [] + self.structure.signal_dependencies[target].extend(deps) + + def get_input_ports(self) -> List[str]: + """获取输入端口名列表""" + return [p.name for p in self.structure.ports if p.direction == 'input'] + + def get_output_ports(self) -> List[str]: + """获取输出端口名列表""" + return [p.name for p in self.structure.ports if p.direction == 'output'] + + +# ============================================================================ +# 功能点识别器 +# ============================================================================ + +class FunctionPointRecognizer: + """ + 功能点识别器 + 自动识别RTL代码中的关键功能点 + """ + + def __init__(self, rtl_code: str, structure: RTLStructure): + """ + Args: + rtl_code: RTL源代码 + structure: 已解析的结构信息 + """ + self.code = rtl_code + self.structure = structure + self.function_points: List[FunctionPoint] = [] + + def recognize(self) -> List[FunctionPoint]: + """执行完整识别""" + self._recognize_fsm() + self._recognize_counters() + self._recognize_conditions() + self._recognize_protocols() + self._recognize_exceptions() + return self.function_points + + def _recognize_fsm(self): + """识别状态机""" + # 查找状态参数定义 + state_params = [] + param_pattern = r'(?:parameter|localparam)\s+([^;]+);' + for match in re.finditer(param_pattern, self.code): + params_str = match.group(1) + # 解析状态定义,如 WL=0, WR=1, ... + state_defs = re.findall(r'([A-Z][A-Z0-9_]*)\s*=\s*(\d+)', params_str) + if state_defs: + state_params.extend([s[0] for s in state_defs]) + + if not state_params: + return + + # 查找 case 语句(状态转换逻辑) + case_pattern = r'case\s*\(\s*(\w+)\s*\)(.*?)(?:endcase|$)' + for match in re.finditer(case_pattern, self.code, re.DOTALL): + state_var = match.group(1) + case_body = match.group(2) + + # 提取状态转换信息 + transitions = self._extract_fsm_transitions(case_body, state_params) + + start_line = self.code[:match.start()].count('\n') + 1 + end_line = self.code[:match.end()].count('\n') + 1 + + fp = FunctionPoint( + name=f"FSM_{state_var}", + fp_type=FunctionPointType.FSM, + location=(start_line, end_line), + code_region=match.group(0), + attributes={ + 'state_variable': state_var, + 'states': state_params, + 'transitions': transitions + } + ) + self.function_points.append(fp) + + def _extract_fsm_transitions(self, case_body: str, states: List[str]) -> Dict[str, List[Dict]]: + """提取 FSM 状态转换信息""" + transitions = {} + + # 匹配每个状态分支 + branch_pattern = r'([A-Z][A-Z0-9_]*)\s*:(.*?)(?=[A-Z][A-Z0-9_]*\s*:|endcase|default:)' + for match in re.finditer(branch_pattern, case_body, re.DOTALL): + current_state = match.group(1) + branch_code = match.group(2) + + if current_state not in states: + continue + + transitions[current_state] = [] + + # 提取条件转换 + cond_pattern = r'(?:if|else\s*if)\s*\(([^)]+)\)\s*(?:next\s*=?|state\s*<=)\s*(\w+)' + for cond_match in re.finditer(cond_pattern, branch_code): + condition = cond_match.group(1).strip() + next_state = cond_match.group(2).strip() + transitions[current_state].append({ + 'condition': condition, + 'next_state': next_state + }) + + # 提取 default 转换 + default_pattern = r'else\s*(?:next\s*=?|state\s*<=)\s*(\w+)' + default_match = re.search(default_pattern, branch_code) + if default_match: + transitions[current_state].append({ + 'condition': 'default', + 'next_state': default_match.group(1) + }) + + return transitions + + def _recognize_counters(self): + """识别计数器""" + # 查找自增/自减操作 + counter_patterns = [ + r'(\w+)\s*<=\s*\1\s*\+\s*1', # counter <= counter + 1 + r'(\w+)\s*<=\s*\1\s*-\s*1', # counter <= counter - 1 + r'(\w+)\s*=\s*\1\s*\+\s*1', # counter = counter + 1 + r'(\w+)\s*=\s*\1\s*-\s*1', # counter = counter - 1 + ] + + counter_candidates = set() + for pattern in counter_patterns: + for match in re.finditer(pattern, self.code): + counter_candidates.add(match.group(1)) + + for counter_name in counter_candidates: + # 查找计数器的边界条件 + boundary_pattern = rf'{counter_name}\s*(>=|<=|==|>|<)\s*(\d+)' + boundaries = [] + for match in re.finditer(boundary_pattern, self.code): + op = match.group(1) + value = int(match.group(2)) + boundaries.append({'operator': op, 'value': value}) + + # 查找计数器位宽 + width = 1 + for signal in self.structure.signals: + if signal.name == counter_name: + width = signal.width + break + + # 查找计数器位置 + pattern = rf'{counter_name}\s*<=' + match = re.search(pattern, self.code) + if match: + start_line = self.code[:match.start()].count('\n') + 1 + end_line = start_line + else: + start_line, end_line = 0, 0 + + fp = FunctionPoint( + name=f"Counter_{counter_name}", + fp_type=FunctionPointType.COUNTER, + location=(start_line, end_line), + code_region="", + attributes={ + 'counter_name': counter_name, + 'width': width, + 'boundaries': boundaries + } + ) + self.function_points.append(fp) + + def _recognize_conditions(self): + """识别条件分支""" + # 查找 if-else 嵌套 + if_pattern = r'(if\s*\([^)]+\)(?:\s*begin)?(?:[^;]*?)(?:end)?(?:\s*else\s*(?:if\s*\([^)]+\))?(?:\s*begin)?(?:[^;]*?)(?:end)?)*)' + + # 简化:查找独立的 if 语句 + simple_if_pattern = r'if\s*\(([^)]+)\)' + for i, match in enumerate(re.finditer(simple_if_pattern, self.code)): + condition = match.group(1).strip() + + start_line = self.code[:match.start()].count('\n') + 1 + end_line = start_line + + # 分析条件中的边界值 + boundary_values = re.findall(r'(>=|<=|==|>|<)\s*(\d+)', condition) + + fp = FunctionPoint( + name=f"Condition_{i+1}", + fp_type=FunctionPointType.CONDITION, + location=(start_line, end_line), + code_region=match.group(0), + attributes={ + 'condition': condition, + 'boundary_values': boundary_values + } + ) + self.function_points.append(fp) + + def _recognize_protocols(self): + """识别协议接口""" + # 查找握手信号 + handshake_patterns = [ + (r'\b(valid|vld)\b', 'valid'), + (r'\b(ready|rdy)\b', 'ready'), + (r'\b(ack|acknowledge)\b', 'ack'), + ] + + handshake_signals = {} + for pattern, signal_type in handshake_patterns: + for match in re.finditer(pattern, self.code, re.IGNORECASE): + signal_name = match.group(1) + if signal_type not in handshake_signals: + handshake_signals[signal_type] = [] + handshake_signals[signal_type].append(signal_name) + + if len(handshake_signals) >= 2: + fp = FunctionPoint( + name="Protocol_Handshake", + fp_type=FunctionPointType.PROTOCOL, + location=(0, 0), + code_region="", + attributes={ + 'protocol_type': 'handshake', + 'signals': handshake_signals + } + ) + self.function_points.append(fp) + + def _recognize_exceptions(self): + """识别异常处理""" + # 查找 error 信号 + error_pattern = r'(?:output|wire)\s+(?:\[[\d:]+\]\s+)?(\w*error\w*)' + for match in re.finditer(error_pattern, self.code, re.IGNORECASE): + error_signal = match.group(1) + fp = FunctionPoint( + name=f"Exception_{error_signal}", + fp_type=FunctionPointType.EXCEPTION, + location=(0, 0), + code_region="", + attributes={ + 'error_signal': error_signal + } + ) + self.function_points.append(fp) + + # 查找 default 分支(case 语句的异常处理) + default_pattern = r'default\s*:' + if re.search(default_pattern, self.code): + fp = FunctionPoint( + name="Exception_DefaultCase", + fp_type=FunctionPointType.EXCEPTION, + location=(0, 0), + code_region="", + attributes={ + 'type': 'default_case' + } + ) + self.function_points.append(fp) + + +# ============================================================================ +# 重要性评分器 +# ============================================================================ + +class ImportanceScorer: + """ + 重要性评分器 + 为每个功能点计算语义重要性评分 + """ + + # 类型权重 + TYPE_WEIGHTS = { + FunctionPointType.FSM: 0.30, + FunctionPointType.COUNTER: 0.20, + FunctionPointType.PROTOCOL: 0.20, + FunctionPointType.CONDITION: 0.15, + FunctionPointType.EXCEPTION: 0.15, + } + + def __init__(self, function_points: List[FunctionPoint], structure: RTLStructure): + """ + Args: + function_points: 功能点列表 + structure: RTL结构信息 + """ + self.function_points = function_points + self.structure = structure + + def score(self) -> List[FunctionPoint]: + """计算所有功能点的重要性评分""" + for fp in self.function_points: + fp.importance_score = self._calculate_score(fp) + + # 按评分排序 + self.function_points.sort(key=lambda x: x.importance_score, reverse=True) + return self.function_points + + def _calculate_score(self, fp: FunctionPoint) -> float: + """计算单个功能点的评分""" + # 基础类型权重 + base_score = self.TYPE_WEIGHTS.get(fp.fp_type, 0.10) + + # 边界条件得分 + boundary_score = self._get_boundary_score(fp) + + # 异常路径得分 + exception_score = self._get_exception_score(fp) + + # 依赖深度得分 + dependency_score = self._get_dependency_score(fp) + + # 复杂度得分 + complexity_score = self._get_complexity_score(fp) + + # 综合评分 + total_score = ( + base_score + + 0.30 * boundary_score + + 0.25 * exception_score + + 0.20 * dependency_score + + 0.25 * complexity_score + ) + + return min(1.0, total_score) # 归一化到 [0, 1] + + def _get_boundary_score(self, fp: FunctionPoint) -> float: + """获取边界条件得分""" + if fp.fp_type == FunctionPointType.COUNTER: + boundaries = fp.attributes.get('boundaries', []) + if boundaries: + # 有边界值的计数器更重要 + return min(1.0, len(boundaries) * 0.3) + + if fp.fp_type == FunctionPointType.CONDITION: + boundary_values = fp.attributes.get('boundary_values', []) + if boundary_values: + return min(1.0, len(boundary_values) * 0.2) + + if fp.fp_type == FunctionPointType.FSM: + states = fp.attributes.get('states', []) + # 状态越多,边界条件越重要 + return min(1.0, len(states) * 0.1) + + return 0.0 + + def _get_exception_score(self, fp: FunctionPoint) -> float: + """获取异常路径得分""" + if fp.fp_type == FunctionPointType.EXCEPTION: + return 1.0 + + if fp.fp_type == FunctionPointType.FSM: + transitions = fp.attributes.get('transitions', {}) + # 检查是否有异常状态转换 + for state, trans_list in transitions.items(): + for trans in trans_list: + if 'error' in trans.get('next_state', '').lower(): + return 0.5 + + return 0.0 + + def _get_dependency_score(self, fp: FunctionPoint) -> float: + """获取依赖深度得分""" + # 分析功能点在信号依赖图中的深度 + if fp.fp_type == FunctionPointType.FSM: + state_var = fp.attributes.get('state_variable', '') + if state_var in self.structure.signal_dependencies: + depth = self._calculate_dependency_depth(state_var, set()) + return min(1.0, depth * 0.1) + + return 0.0 + + def _calculate_dependency_depth(self, signal: str, visited: set) -> int: + """递归计算依赖深度""" + if signal in visited: + return 0 + visited.add(signal) + + deps = self.structure.signal_dependencies.get(signal, []) + if not deps: + return 0 + + max_depth = 0 + for dep in deps: + depth = self._calculate_dependency_depth(dep, visited) + max_depth = max(max_depth, depth) + + return max_depth + 1 + + def _get_complexity_score(self, fp: FunctionPoint) -> float: + """获取复杂度得分""" + if fp.fp_type == FunctionPointType.FSM: + transitions = fp.attributes.get('transitions', {}) + # 状态转换越多,复杂度越高 + total_transitions = sum(len(t) for t in transitions.values()) + return min(1.0, total_transitions * 0.05) + + if fp.fp_type == FunctionPointType.CONDITION: + condition = fp.attributes.get('condition', '') + # 条件越复杂(包含的逻辑运算符越多),复杂度越高 + operators = len(re.findall(r'(&&|\|\|)', condition)) + return min(1.0, operators * 0.2) + + return 0.0 + + +# ============================================================================ +# 测试场景生成器 +# ============================================================================ + +class TestScenarioGenerator: + """ + 测试场景生成器 + 基于识别的功能点生成测试场景建议 + """ + + SCENARIO_TEMPLATES = { + FunctionPointType.FSM: [ + ("state_transition", "测试所有状态转换路径"), + ("illegal_transition", "测试非法状态转换"), + ("state_loop", "测试状态循环"), + ], + FunctionPointType.COUNTER: [ + ("boundary_zero", "测试计数器归零"), + ("boundary_max", "测试计数器最大值"), + ("overflow", "测试计数器溢出"), + ], + FunctionPointType.CONDITION: [ + ("branch_path", "测试各分支路径"), + ("boundary_condition", "测试边界条件"), + ("combination", "测试组合条件"), + ], + FunctionPointType.PROTOCOL: [ + ("handshake_timing", "测试握手时序"), + ("concurrent", "测试并发操作"), + ("timeout", "测试协议超时"), + ], + FunctionPointType.EXCEPTION: [ + ("trigger", "测试异常触发"), + ("recovery", "测试异常恢复"), + ("fault_tolerance", "测试容错处理"), + ], + } + + def __init__(self, function_points: List[FunctionPoint]): + """ + Args: + function_points: 带评分的功能点列表 + """ + self.function_points = function_points + + def generate(self) -> List[TestScenario]: + """生成测试场景建议""" + scenarios = [] + + for fp in self.function_points: + templates = self.SCENARIO_TEMPLATES.get(fp.fp_type, []) + for template_name, description in templates: + # 根据重要性评分计算优先级 + priority = fp.importance_score + + # 生成具体的场景描述 + specific_desc = self._customize_description(fp, template_name, description) + + scenario = TestScenario( + name=f"{fp.name}_{template_name}", + target_function=fp.name, + description=specific_desc, + priority=priority, + scenario_type=template_name + ) + scenarios.append(scenario) + + # 按优先级排序 + scenarios.sort(key=lambda x: x.priority, reverse=True) + return scenarios + + def _customize_description(self, fp: FunctionPoint, template_name: str, base_desc: str) -> str: + """根据功能点属性定制描述""" + if fp.fp_type == FunctionPointType.FSM: + states = fp.attributes.get('states', []) + transitions = fp.attributes.get('transitions', {}) + + if template_name == "state_transition": + return f"测试 {fp.name} 的所有状态转换(共 {len(states)} 个状态,{sum(len(t) for t in transitions.values())} 个转换)" + elif template_name == "illegal_transition": + return f"测试 {fp.name} 的非法状态转换处理" + elif template_name == "state_loop": + return f"测试 {fp.name} 的状态循环场景" + + elif fp.fp_type == FunctionPointType.COUNTER: + counter_name = fp.attributes.get('counter_name', '') + boundaries = fp.attributes.get('boundaries', []) + + if template_name == "boundary_zero": + return f"测试计数器 {counter_name} 归零场景" + elif template_name == "boundary_max": + max_boundary = next((b for b in boundaries if b['operator'] in ['>=', '>']), None) + if max_boundary: + return f"测试计数器 {counter_name} 达到最大值 {max_boundary['value']}" + return f"测试计数器 {counter_name} 最大值边界" + elif template_name == "overflow": + return f"测试计数器 {counter_name} 溢出场景" + + elif fp.fp_type == FunctionPointType.CONDITION: + condition = fp.attributes.get('condition', '') + + if template_name == "branch_path": + return f"测试条件分支: {condition}" + elif template_name == "boundary_condition": + boundary_values = fp.attributes.get('boundary_values', []) + if boundary_values: + values_str = ', '.join([f"{b[0]}{b[1]}" for b in boundary_values]) + return f"测试边界条件: {values_str}" + return f"测试条件边界: {condition}" + + return base_desc + + +# ============================================================================ +# 语义分析器 (主入口) +# ============================================================================ + +class SemanticAnalyzer: + """ + 语义分析器 - 第0层主入口 + 整合所有子模块,提供统一的语义分析接口 + """ + + def __init__(self, rtl_code: str): + """ + Args: + rtl_code: RTL源代码字符串 + """ + self.rtl_code = rtl_code + self.structure: Optional[RTLStructure] = None + self.function_points: List[FunctionPoint] = [] + self.test_scenarios: List[TestScenario] = [] + + def analyze(self) -> Dict[str, Any]: + """ + 执行完整的语义分析 + + Returns: + 包含所有分析结果的字典 + """ + # Step 1: 解析RTL结构 + parser = RTLStructureParser(self.rtl_code) + self.structure = parser.parse() + + # Step 2: 识别功能点 + recognizer = FunctionPointRecognizer(self.rtl_code, self.structure) + self.function_points = recognizer.recognize() + + # Step 3: 计算重要性评分 + scorer = ImportanceScorer(self.function_points, self.structure) + self.function_points = scorer.score() + + # Step 4: 生成测试场景 + generator = TestScenarioGenerator(self.function_points) + self.test_scenarios = generator.generate() + + return self.get_analysis_result() + + # def get_analysis_result(self) -> Dict[str, Any]: + # """获取分析结果""" + # return { + # 'module_name': self.structure.module_name if self.structure else '', + # 'inputs': self.structure.get_input_ports() if self.structure else [], + # 'outputs': self.structure.get_output_ports() if self.structure else [], + # 'function_points': [ + # { + # 'name': fp.name, + # 'type': fp.fp_type.value, + # 'importance': fp.importance_score, + # 'covered': fp.covered, + # 'attributes': fp.attributes + # } + # for fp in self.function_points + # ], + # 'test_scenarios': [ + # { + # 'name': ts.name, + # 'target': ts.target_function, + # 'description': ts.description, + # 'priority': ts.priority + # } + # for ts in self.test_scenarios + # ] + # } + + def get_analysis_result(self) -> Dict[str, Any]: + """获取分析结果""" + # 从 structure.ports 中获取输入输出端口 + inputs = [p.name for p in self.structure.ports if p.direction == 'input'] if self.structure else [] + outputs = [p.name for p in self.structure.ports if p.direction == 'output'] if self.structure else [] + + return { + 'module_name': self.structure.module_name if self.structure else '', + 'inputs': inputs, + 'outputs': outputs, + 'ports': [ + { + 'name': port.name, + 'direction': port.direction, + 'width': port.width, + 'msb': port.msb, + 'lsb': port.lsb + } + for port in self.structure.ports + ] if self.structure else [], + 'function_points': [ + { + 'name': fp.name, + 'type': fp.fp_type.value, + 'importance': fp.importance_score, + 'covered': fp.covered, + 'location': { + 'start_line': fp.location[0], + 'end_line': fp.location[1] + }, + 'attributes': fp.attributes + } + for fp in self.function_points + ], + 'test_scenarios': [ + { + 'name': ts.name, + 'target': ts.target_function, + 'description': ts.description, + 'priority': ts.priority + } + for ts in self.test_scenarios + ] + } + + def get_function_points_by_type(self, fp_type: FunctionPointType) -> List[FunctionPoint]: + """按类型获取功能点""" + return [fp for fp in self.function_points if fp.fp_type == fp_type] + + def get_top_priority_scenarios(self, n: int = 5) -> List[TestScenario]: + """获取优先级最高的N个测试场景""" + return self.test_scenarios[:n] + + def get_fsm_info(self) -> Optional[Dict[str, Any]]: + """获取FSM相关信息(如果有)""" + fsm_fps = self.get_function_points_by_type(FunctionPointType.FSM) + if fsm_fps: + fp = fsm_fps[0] + return { + 'name': fp.name, + 'state_variable': fp.attributes.get('state_variable', ''), + 'states': fp.attributes.get('states', []), + 'transitions': fp.attributes.get('transitions', {}) + } + return None + + # def generate_prompt_context(self) -> str: + # """生成用于 Prompt 的上下文信息""" + # context = [] + + # # 端口信息 + # if self.structure: + # context.append("[MODULE INFO]") + # context.append(f"Module: {self.structure.module_name}") + # # context.append(f"Inputs: {', '.join(self.structure.get_input_ports())}") + # context.append(f"inputs = [p.name for p in self.structure.ports if p.direction == 'input']") + # context.append(f"Outputs: {', '.join(self.structure.get_output_ports())}") + # context.append("") + + # # 功能点信息 + # if self.function_points: + # context.append("[FUNCTION POINTS - Ranked by Importance]") + # for i, fp in enumerate(self.function_points[:10]): # Top 10 + # context.append(f"{i+1}. {fp.name} ({fp.fp_type.value}): importance={fp.importance_score:.2f}") + # context.append("") + + # # FSM 详细信息 + # fsm_info = self.get_fsm_info() + # if fsm_info: + # context.append("[FSM DETAILS]") + # context.append(f"State Variable: {fsm_info['state_variable']}") + # context.append(f"States: {', '.join(fsm_info['states'])}") + # context.append("Transitions:") + # for state, trans_list in fsm_info['transitions'].items(): + # for trans in trans_list: + # context.append(f" {state} --[{trans['condition']}]--> {trans['next_state']}") + # context.append("") + + # # 测试场景建议 + # if self.test_scenarios: + # context.append("[RECOMMENDED TEST SCENARIOS]") + # for i, ts in enumerate(self.test_scenarios[:5]): + # context.append(f"{i+1}. {ts.name}: {ts.description} (priority={ts.priority:.2f})") + + # return "\n".join(context) + + + def generate_prompt_context(self) -> str: + """生成用于 Prompt 的上下文信息""" + context = [] + + # 端口信息 + if self.structure: + inputs = [p.name for p in self.structure.ports if p.direction == 'input'] + outputs = [p.name for p in self.structure.ports if p.direction == 'output'] + context.append("[MODULE INFO]") + context.append(f"Module: {self.structure.module_name}") + context.append(f"Inputs: {', '.join(inputs)}") + context.append(f"Outputs: {', '.join(outputs)}") + context.append("") + + # 功能点信息 + if self.function_points: + context.append("[FUNCTION POINTS - Ranked by Importance]") + for i, fp in enumerate(self.function_points[:10]): # Top 10 + context.append(f"{i+1}. {fp.name} ({fp.fp_type.value}): importance={fp.importance_score:.2f}") + context.append("") + + # FSM 详细信息 + fsm_info = self.get_fsm_info() + if fsm_info: + context.append("[FSM DETAILS]") + context.append(f"State Variable: {fsm_info['state_variable']}") + context.append(f"States: {', '.join(fsm_info['states'])}") + context.append("Transitions:") + for state, trans_list in fsm_info['transitions'].items(): + for trans in trans_list: + context.append(f" {state} --[{trans['condition']}]--> {trans['next_state']}") + context.append("") + + # 测试场景建议 + if self.test_scenarios: + context.append("[RECOMMENDED TEST SCENARIOS]") + for i, ts in enumerate(self.test_scenarios[:5]): + context.append(f"{i+1}. {ts.name}: {ts.description} (priority={ts.priority:.2f})") + + return "\n".join(context) + +# ============================================================================ +# 便捷函数 +# ============================================================================ + +def analyze_rtl(rtl_code: str) -> Dict[str, Any]: + """ + 便捷函数:分析 RTL 代码 + + Args: + rtl_code: RTL源代码字符串 + + Returns: + 分析结果字典 + """ + analyzer = SemanticAnalyzer(rtl_code) + return analyzer.analyze() + + +def get_fsm_states(rtl_code: str) -> List[str]: + """ + 便捷函数:获取 FSM 状态列表 + + Args: + rtl_code: RTL源代码字符串 + + Returns: + 状态名列表 + """ + analyzer = SemanticAnalyzer(rtl_code) + analyzer.analyze() + fsm_info = analyzer.get_fsm_info() + return fsm_info['states'] if fsm_info else [] diff --git a/autoline/test_history.py b/autoline/test_history.py new file mode 100644 index 0000000..e290b00 --- /dev/null +++ b/autoline/test_history.py @@ -0,0 +1,580 @@ +""" +Description : Test History Manager (Layer 1 Support Module) + - Store and manage test case history + - Support sequence pattern analysis + - Provide diversity statistics +Author : CGA Enhancement Project +Time : 2026/03/16 +""" + +import json +import os +import logging +from typing import List, Dict, Optional, Any, Tuple, Set +from dataclasses import dataclass, field, asdict +from datetime import datetime +from collections import defaultdict +import hashlib +import re + +logger = logging.getLogger(__name__) + + +# ============================================================================ +# 数据结构定义 +# ============================================================================ + +@dataclass +class InputSequence: + """ + 输入序列记录 + + Attributes: + signal_name: 信号名称 + values: 赋值序列 [(time, value), ...] + """ + signal_name: str + values: List[Tuple[int, Any]] = field(default_factory=list) + + def to_pattern_string(self) -> str: + """转换为模式字符串(仅包含值)""" + return "->".join(str(v[1]) for v in self.values) + + def get_hash(self) -> str: + """获取序列哈希值""" + return hashlib.md5(self.to_pattern_string().encode()).hexdigest()[:8] + + +@dataclass +class TestRecord: + """ + 测试用例记录 + + Attributes: + test_id: 测试ID + code: 生成的测试代码 + input_sequences: 输入信号序列列表 + target_function: 目标功能点 + covered_lines: 覆盖的代码行 + covered_functions: 覆盖的功能点 + coverage_score: 覆盖率分数 + diversity_scores: 多样性得分字典 + iteration: 迭代次数 + timestamp: 时间戳 + success: 是否成功 + """ + test_id: str + code: str = "" + input_sequences: List[InputSequence] = field(default_factory=list) + target_function: str = "" + covered_lines: List[int] = field(default_factory=list) + covered_functions: List[str] = field(default_factory=list) + coverage_score: float = 0.0 + diversity_scores: Dict[str, float] = field(default_factory=dict) + iteration: int = 0 + timestamp: str = field(default_factory=lambda: datetime.now().isoformat()) + success: bool = False + + def get_sequence_patterns(self) -> Dict[str, str]: + """获取所有输入序列的模式""" + return {seq.signal_name: seq.to_pattern_string() for seq in self.input_sequences} + + +@dataclass +class SequencePattern: + """ + 序列模式统计 + + Attributes: + pattern: 模式字符串 + count: 出现次数 + signal_name: 所属信号 + test_ids: 关联的测试ID列表 + """ + pattern: str + count: int = 0 + signal_name: str = "" + test_ids: List[str] = field(default_factory=list) + + def is_overused(self, threshold: int = 3) -> bool: + """判断是否过度使用""" + return self.count >= threshold + + +# ============================================================================ +# 序列提取器 +# ============================================================================ + +class SequenceExtractor: + """ + 从测试代码中提取输入序列 + + 解析Verilog测试代码,提取信号赋值序列 + """ + + # 匹配信号赋值语句 + ASSIGNMENT_PATTERNS = [ + # 阻塞赋值: signal = value; + r'(\w+)\s*=\s*([0-9]+\'[bdh][0-9a-fA-FxXzZ_]+|\d+|x|z)\s*;', + # 非阻塞赋值: signal <= value; + r'(\w+)\s*<=\s*([0-9]+\'[bdh][0-9a-fA-FxXzZ_]+|\d+|x|z)\s*;', + # 简单赋值(无位宽) + r'(\w+)\s*=\s*(\d+)\s*;', + ] + + # 匹配延时 + DELAY_PATTERN = r'#\s*(\d+)\s*;' + + # 匹配时钟周期等待 + CLOCK_WAIT_PATTERN = r'repeat\s*\(\s*(\d+)\s*\)\s*@\s*\(\s*posedge\s+(\w+)\s*\)' + + def __init__(self): + self.known_signals: Set[str] = set() + + def set_known_signals(self, signals: List[str]): + """设置已知信号列表(用于过滤)""" + self.known_signals = set(signals) + + def extract(self, code: str) -> List[InputSequence]: + """ + 从代码中提取输入序列 + + Args: + code: Verilog测试代码 + + Returns: + 输入序列列表 + """ + sequences = {} + current_time = 0 + + # 按行处理代码 + lines = code.split('\n') + + for line in lines: + line = line.strip() + + # 跳过注释和空行 + if not line or line.startswith('//'): + continue + + # 检测延时,更新时间 + delay_match = re.search(self.DELAY_PATTERN, line) + if delay_match: + current_time += int(delay_match.group(1)) + continue + + # 检测时钟周期等待 + clock_match = re.search(self.CLOCK_WAIT_PATTERN, line, re.IGNORECASE) + if clock_match: + cycles = int(clock_match.group(1)) + current_time += cycles * 10 # 假设每周期10时间单位 + continue + + # 检测赋值语句 + for pattern in self.ASSIGNMENT_PATTERNS: + matches = re.finditer(pattern, line, re.IGNORECASE) + for match in matches: + signal = match.group(1) + value = match.group(2) + + # 过滤非目标信号 + if self.known_signals and signal not in self.known_signals: + continue + + # 跳过明显的非输入信号 + if signal.lower() in ['i', 'j', 'k', 'cnt', 'count', 'temp']: + continue + + if signal not in sequences: + sequences[signal] = InputSequence(signal_name=signal) + + sequences[signal].values.append((current_time, value)) + current_time += 1 # 赋值语句本身占用1时间单位 + + return list(sequences.values()) + + +# ============================================================================ +# 测试历史管理器 +# ============================================================================ + +class TestHistoryManager: + """ + 测试历史管理器 + + 管理已生成测试用例的历史记录,支持: + - 测试用例存储和检索 + - 序列模式统计分析 + - 多样性分布统计 + """ + + def __init__(self, history_file: str = None): + """ + Args: + history_file: 历史记录文件路径(可选) + """ + + #必须先保存 history_file,否则 save() 方法无法找到文件路径 + self.history_file = history_file + + self.records: List[TestRecord] = [] + self.patterns: Dict[str, SequencePattern] = {} # pattern_hash -> SequencePattern + self.signal_patterns: Dict[str, List[str]] = defaultdict(list) # signal_name -> [pattern_hashes] + self.sequence_extractor = SequenceExtractor() + + # 统计信息 + self.stats = { + 'total_tests': 0, + 'successful_tests': 0, + 'total_coverage': 0.0, + 'avg_diversity': 0.0 + } + + if history_file and os.path.exists(history_file): + self.load(history_file) + + # ==================== 记录管理 ==================== + + def add_record(self, + code: str, + test_id: str = None, + target_function: str = "", + covered_lines: List[int] = None, + covered_functions: List[str] = None, + coverage_score: float = 0.0, + iteration: int = 0, + success: bool = False, + known_signals: List[str] = None) -> TestRecord: + """ + 添加测试记录 + + Args: + code: 测试代码 + test_id: 测试ID(自动生成如果未提供) + target_function: 目标功能点 + covered_lines: 覆盖的代码行 + covered_functions: 覆盖的功能点 + coverage_score: 覆盖率分数 + iteration: 迭代次数 + success: 是否成功 + known_signals: 已知信号列表 + + Returns: + 创建的测试记录 + """ + if test_id is None: + test_id = f"test_{len(self.records)}_{datetime.now().strftime('%H%M%S')}" + + # 提取输入序列 + if known_signals: + self.sequence_extractor.set_known_signals(known_signals) + input_sequences = self.sequence_extractor.extract(code) + + # 创建记录 + record = TestRecord( + test_id=test_id, + code=code, + input_sequences=input_sequences, + target_function=target_function, + covered_lines=covered_lines or [], + covered_functions=covered_functions or [], + coverage_score=coverage_score, + iteration=iteration, + success=success + ) + + self.records.append(record) + + # 更新模式统计 + self._update_patterns(record) + + # 更新统计信息 + self._update_stats() + + logger.debug(f"Added test record: {test_id}, sequences: {len(input_sequences)}") + + return record + + def get_record(self, test_id: str) -> Optional[TestRecord]: + """根据ID获取记录""" + for record in self.records: + if record.test_id == test_id: + return record + return None + + def get_recent_records(self, n: int = 10) -> List[TestRecord]: + """获取最近的N条记录""" + return self.records[-n:] if len(self.records) >= n else self.records + + def get_successful_records(self) -> List[TestRecord]: + """获取所有成功的记录""" + return [r for r in self.records if r.success] + + # ==================== 模式分析 ==================== + + def _update_patterns(self, record: TestRecord): + """更新序列模式统计""" + for seq in record.input_sequences: + pattern_str = seq.to_pattern_string() + pattern_hash = seq.get_hash() + + if pattern_hash not in self.patterns: + self.patterns[pattern_hash] = SequencePattern( + pattern=pattern_str, + count=1, + signal_name=seq.signal_name, + test_ids=[record.test_id] + ) + else: + self.patterns[pattern_hash].count += 1 + self.patterns[pattern_hash].test_ids.append(record.test_id) + + # 按信号索引 + if pattern_hash not in self.signal_patterns[seq.signal_name]: + self.signal_patterns[seq.signal_name].append(pattern_hash) + + def get_overused_patterns(self, threshold: int = 3) -> List[SequencePattern]: + """ + 获取过度使用的模式 + + Args: + threshold: 过度使用阈值 + + Returns: + 过度使用的模式列表 + """ + return [p for p in self.patterns.values() if p.is_overused(threshold)] + + def get_common_patterns(self, top_n: int = 5) -> List[Tuple[str, int]]: + """ + 获取最常见的模式 + + Args: + top_n: 返回数量 + + Returns: + [(pattern, count), ...] + """ + sorted_patterns = sorted( + self.patterns.items(), + key=lambda x: x[1].count, + reverse=True + ) + return [(p[1].pattern, p[1].count) for p in sorted_patterns[:top_n]] + + def get_pattern_for_signal(self, signal_name: str) -> List[SequencePattern]: + """获取特定信号的所有模式""" + pattern_hashes = self.signal_patterns.get(signal_name, []) + return [self.patterns[h] for h in pattern_hashes if h in self.patterns] + + # ==================== 多样性分析 ==================== + + def calculate_sequence_diversity(self, new_sequences: List[InputSequence]) -> float: + """ + 计算新序列与历史记录的多样性得分 + + Args: + new_sequences: 新的输入序列列表 + + Returns: + 多样性得分 (0.0 - 1.0) + """ + if not self.records: + return 1.0 # 没有历史记录时,认为完全多样 + + if not new_sequences: + return 0.0 # 没有序列时,多样性为0 + + # 检查模式重复度 + new_patterns = {seq.get_hash() for seq in new_sequences} + total_patterns = len(new_patterns) + + if total_patterns == 0: + return 0.0 + + # 计算新模式比例 + new_pattern_count = sum(1 for h in new_patterns if h not in self.patterns) + pattern_diversity = new_pattern_count / total_patterns + + return pattern_diversity + + def calculate_edit_distance_diversity(self, new_code: str) -> float: + """ + 基于编辑距离计算多样性 + + 使用简化的编辑距离计算 + """ + if not self.records: + return 1.0 + + # 获取最近的记录作为参考 + recent_records = self.get_recent_records(5) + + min_distance = float('inf') + for record in recent_records: + distance = self._levenshtein_distance(new_code, record.code) + min_distance = min(min_distance, distance) + + # 归一化到 [0, 1] + max_len = max(len(new_code), max(len(r.code) for r in recent_records)) + if max_len == 0: + return 0.0 + + return min_distance / max_len + + def _levenshtein_distance(self, s1: str, s2: str) -> int: + """计算Levenshtein编辑距离(简化版)""" + if len(s1) < len(s2): + return self._levenshtein_distance(s2, s1) + + if len(s2) == 0: + return len(s1) + + # 使用简化的计算(抽样) + if len(s1) > 500: + s1 = s1[:500] + if len(s2) > 500: + s2 = s2[:500] + + previous_row = range(len(s2) + 1) + for i, c1 in enumerate(s1): + current_row = [i + 1] + for j, c2 in enumerate(s2): + insertions = previous_row[j + 1] + 1 + deletions = current_row[j] + 1 + substitutions = previous_row[j] + (c1 != c2) + current_row.append(min(insertions, deletions, substitutions)) + previous_row = current_row + + return previous_row[-1] + + # ==================== 统计信息 ==================== + + def _update_stats(self): + """更新统计信息""" + self.stats['total_tests'] = len(self.records) + self.stats['successful_tests'] = sum(1 for r in self.records if r.success) + + if self.records: + self.stats['total_coverage'] = sum(r.coverage_score for r in self.records) + self.stats['avg_coverage'] = self.stats['total_coverage'] / len(self.records) + + def get_statistics(self) -> Dict[str, Any]: + """获取统计信息""" + return { + **self.stats, + 'total_patterns': len(self.patterns), + 'overused_patterns': len(self.get_overused_patterns()), + 'unique_signals': len(self.signal_patterns) + } + + def get_diversity_report(self) -> str: + """生成多样性报告""" + lines = [] + lines.append("=" * 50) + lines.append("TEST HISTORY DIVERSITY REPORT") + lines.append("=" * 50) + lines.append(f"Total Tests: {self.stats['total_tests']}") + lines.append(f"Successful Tests: {self.stats['successful_tests']}") + lines.append(f"Total Patterns: {len(self.patterns)}") + lines.append("") + + # 常见模式 + lines.append("TOP 5 COMMON PATTERNS:") + common = self.get_common_patterns(5) + for i, (pattern, count) in enumerate(common, 1): + lines.append(f" {i}. {pattern[:40]}... (x{count})") + + # 过度使用的模式 + overused = self.get_overused_patterns() + if overused: + lines.append("") + lines.append("OVERUSED PATTERNS (need diversification):") + for p in overused[:5]: + lines.append(f" - {p.signal_name}: {p.pattern[:30]}... (used {p.count} times)") + + lines.append("=" * 50) + return "\n".join(lines) + + # ==================== 持久化 ==================== + + def save(self, filepath: str = None): + """保存历史记录到文件""" + filepath = filepath or self.history_file + if not filepath: + return + + # 手动构建可序列化的数据结构 + records_data = [] + for r in self.records: + record_dict = { + 'test_id': r.test_id, + 'code': r.code, + 'input_sequences': [], + 'target_function': r.target_function, + 'covered_lines': r.covered_lines, + 'covered_functions': r.covered_functions, + 'coverage_score': r.coverage_score, + 'diversity_scores': r.diversity_scores, + 'iteration': r.iteration, + 'timestamp': r.timestamp, + 'success': r.success + } + # 手动转换 InputSequence 对象 + for seq in r.input_sequences: + record_dict['input_sequences'].append({ + 'signal_name': seq.signal_name, + 'values': seq.values + }) + records_data.append(record_dict) + + data = { + 'records': records_data, + 'stats': self.stats + } + + with open(filepath, 'w', encoding='utf-8') as f: + json.dump(data, f, indent=2, ensure_ascii=False) + + logger.info(f"Test history saved to {filepath}") + + def load(self, filepath: str): + """从文件加载历史记录""" + if not os.path.exists(filepath): + return + + with open(filepath, 'r', encoding='utf-8') as f: + data = json.load(f) + + self.records = [] + for r in data.get('records', []): + sequences = [ + InputSequence(**s) for s in r.get('input_sequences', []) + ] + record = TestRecord( + test_id=r['test_id'], + code=r['code'], + input_sequences=sequences, + target_function=r.get('target_function', ''), + covered_lines=r.get('covered_lines', []), + covered_functions=r.get('covered_functions', []), + coverage_score=r.get('coverage_score', 0.0), + iteration=r.get('iteration', 0), + timestamp=r.get('timestamp', ''), + success=r.get('success', False) + ) + self.records.append(record) + self._update_patterns(record) + + self.stats = data.get('stats', self.stats) + logger.info(f"Loaded {len(self.records)} test records from {filepath}") + + +# ============================================================================ +# 便捷函数 +# ============================================================================ + +def create_test_history(history_file: str = None) -> TestHistoryManager: + """创建测试历史管理器""" + return TestHistoryManager(history_file=history_file) \ No newline at end of file diff --git a/config/__init__.py b/config/__init__.py new file mode 100644 index 0000000..f47037d --- /dev/null +++ b/config/__init__.py @@ -0,0 +1,8 @@ +""" +Description : description +Author : Ruidi Qiu (r.qiu@tum.de) +Time : 2024/7/3 18:17:10 +LastEdited : 2024/7/3 23:25:11 +""" + +from .config import * \ No newline at end of file diff --git a/config/__pycache__/__init__.cpython-312.pyc b/config/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..68b223285d50ff9e27968fa8e7a7a313272ea320 GIT binary patch literal 338 zcmX@j%ge<81OdMyGnWJD#~=<2FhLogg@BCd3@Hpz3@MB$OgW5enHU)=nKfCey0~0Y zi<65o3raHc^AxNU6ckbrd@je*l8pQ!pdb){+^^Fw_EvyX9tqcvgd=iUGTvIYjQd1yC!BrR;TN#;J85(lE zWCS`=lkpa#mY*imE%x~Ml>FrQ_*-nr`FUxX=_?sNgFN%gR6ip>H&wqXBQY;s-#Ndi zC^fmnDK#%SLqEhRJvA@hv7kU7qC-DEJ~J<~BtBlRpz;@oO>TZlX-=wL5hu_TkWIz> ZK;i>4BO~KSCRRq4uY3%QVnysgF#y&?SA_ro literal 0 HcmV?d00001 diff --git a/config/__pycache__/config.cpython-312.pyc b/config/__pycache__/config.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b82b483be8eae7303d66800c87d21131784d79f5 GIT binary patch literal 13032 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: #str = CFG_CUS_PATH): +# time = get_time() +# hostname, pid = get_runinfo() +# config = load_config_obj(custom_config_path) +# config.run.custom_path = custom_config_path +# config.run.time = time +# config.run.hostname = hostname +# config.run.pid = pid +# if not config.save.en: +# # config.save.log.en = False +# # config.save.message.en = False +# # iterate the attr of config.save, if they have en attr, set it to False +# for attr in DictTodict(config.save).keys(): +# sub_config = getattr(config.save, attr) +# if isinstance(sub_config, Dict): +# try: # will raise keyerror if sub_config doesn't have en attr (it will only return false when come across AttributeError, but Dict doesn't have en attr, so it will raise KeyError) +# hasattr(sub_config, "en") +# except: +# continue +# setattr(sub_config, "en", False) +# if config.gpt.model in LLM_MODEL_REDIRECTION.keys(): +# config.gpt.model = LLM_MODEL_REDIRECTION[config.gpt.model] +# # config_val(config) +# return config + +def load_split_config(custom_config_path = CFG_CUS_PATH, default_config_path = CFG_DEF_PATH): + # no validation. only for printing + # used in loader_saver.py + (custom_cfg_dict, default_cfg_dict) = load_config_dict('split', default_config_path, custom_config_path) + if custom_cfg_dict is None: + # custom_config can be None + custom_cfg = None + else: + custom_cfg = dictToObj(custom_cfg_dict) + default_cfg = dictToObj(default_cfg_dict) + return custom_cfg, default_cfg + +def get_cfg_path_from_alias(custom_cfg_alias: str|int) -> str: + if isinstance(custom_cfg_alias, str): + if custom_cfg_alias.isdigit(): + custom_cfg_filename = "custom" + str(custom_cfg_alias) + "." + "yaml" + config_path = os.path.join("config/configs", custom_cfg_filename) + elif "/" in custom_cfg_alias and ".yaml" in custom_cfg_alias: + config_path = custom_cfg_alias + else: + config_path = os.path.join("config/configs", custom_cfg_alias + "." + "yaml") + elif isinstance(custom_cfg_alias, int): + custom_cfg_filename = "custom" + str(custom_cfg_alias) + "." + "yaml" + config_path = os.path.join("config/configs", custom_cfg_filename) + else: + raise ValueError("Invalid custom config alias: '--config %s'"%(custom_cfg_alias)) + if not os.path.exists(config_path): + raise FileNotFoundError("alias [%s]: The custom config file %s doesn't exist"%(custom_cfg_alias, config_path)) + return config_path + +### test ### +if __name__ == "__main__": + my_config = Config(CFG_CUS_PATH) + print(my_config.get_item("run", "mode")) + print(my_config.run.mode) + print(my_config.get_item("run")) \ No newline at end of file diff --git a/config/custom.yaml b/config/custom.yaml new file mode 100644 index 0000000..ebd09ec --- /dev/null +++ b/config/custom.yaml @@ -0,0 +1,15 @@ + +run: + mode: 'autoline' +gpt: + model: "qwen-max" + key_path: "config/key_API.json" +save: + en: True + root: "/home/zhang/CorrectBench/TBgen_App/output/example_mul/" +autoline: + cga: + enabled: True + max_iter: 10 + promptscript: "pychecker" + onlyrun: "TBgensimeval" diff --git a/config/default.yaml b/config/default.yaml new file mode 100644 index 0000000..a1ec835 --- /dev/null +++ b/config/default.yaml @@ -0,0 +1,96 @@ +################# running ################# +run: + version: '2.0' # last stable version of the code + time: ~ # preserved, will be set in program + custom_path: ~ #preserved, will be set in program + mode: 'qwen-max' # 'chatgpt': like a chatgpt but can load prompt or previous messages. 'autoline': run pipeline of Chatbench automatically. 'iverilog': run iverilog. 'test': test mode. + +################# saving ################## +save: + en: True # True -> save according to settings below; False -> do not save anything. + root: ~ # the root saving dir ends with '/'; will be set in program. + pub: + prefix: # the real name of one experiment, fill it in custom_path; + dir: 'saves/$weekrange$/' # Ended with '/'; if not empty, log/tb/data will share one dir, recommended. + subdir: '' # the final dir of one experiment would be dir + subdir; + + log: + en: True + dir: 'logs/' + notes: ~ + cfg_pmode: iwantall # available: ['split', 'merge', 'iwantall'] # config print mode; see loader_saver.py; + debug_en: False + level: "TRACE" + + message: + en: True + dir: 'messages/' + format: 'json' # 'json' or 'txt' + + iverilog: + en: True + subdir: 'ivcode_nodebug' # for pub mode + +################# loading ################# +load: + prompt: + path: "config/initial_prompts/prompt1.txt" # Only valid when gpt.start_form = "prompt"; path to the prompt file + pick_idx: [] # TODO: valid only when path is ended with '.json', else ignored + + stage_template: + path: "config/templates/stage_template0301.txt" + +############### GPT related ############### +gpt: + model: "4o" # model name. Now we only use "gpt-4-1106-preview" or "gpt-3.5-turbo-1106" (20231128). "3.5" or "4" will be redirected to the above two models. see https://platform.openai.com/docs/models. + key_path: "config/key_API.json" # path to the key file + temperature: ~ # currently only in mode "chatgpt". not valid in mode "autoline" because it is too complicated. For chatgpt, it is default to 1.0. see https://platform.openai.com/docs/api-reference/chat/create. + json_mode: False # if True, the output of GPT will be in json format. Don't forget to ask GPT to return the json format. + chatgpt: # settings below are valid only at mode "chatgpt"; + start_form: "chat" # "chat": input messages by hand; "prompt": input messages from file; only valid for the first message. In the future there will be a new start_form "competion". + # follow_form: "chat" # + one_time_talk: False # will not continue to talk after the first message. + rtlgen_model: ~ # model used in autoline-3-TBcheck-discriminator, if None, use the same model as gpt.model. + +################# iverilog ################ +iverilog: + dir: "" # valid at mode "iverilog"; path to the dir of the iverilog files + task_id: "" # valid at mode "iverilog"; the task id of the iverilog task + +################# autoline ################ +autoline: + result_path: "results" + cga: + enabled: True + max_iter: 10 + target_coverage: 100.0 + probset: # integret probset/mutantset/prob_except; 20240304 + path: ~ # default:"data/HDLBits/HDLBits_data.jsonl", you must enter this path in custom.yaml to avoid unconscious use. + mutant_path: ~ # used in TB_eval2; you must enter this path in custom.yaml to avoid unconscious use. + gptgenRTL_path: ~ # used in TB_eval2b; you must enter this path in custom.yaml to avoid unconscious use. + more_info_paths: [] # more info added to the probset; + only: ['lemmings3','lemmings4','ece241_2013_q8','2014_q3fsm','m2014_q6','review2015_fsm','rule110','fsm_ps2'] # only import data in the list by task_id; + exclude: [] # excludes data in the list by task_id; + exclude_json: ~ # excludes data in the json file by task_id; + filter: [{}] # only imports data containing key-value pairs; example: {"circuit_type": "CMB"} means only import data with "circuit_type" = "CMB"; x + checklist: + max: 3 + debug: + max: 5 # max iterations of debug; if 0, no debug. + reboot: 1 # rerun stage 4 after every x iterations of debug. if 0, not debug but only reboot + py_rollback: 2 # reboot both after every x iterations of py_debug; if 0, no py debug; if 1, only debug 1 time and then reboot; + onlyrun: ~ # valid: [~, "TBgen", "TBgensim", "TBgensimeval"] ; if none, run all tasks; if not none, run only the tasks in the list. + promptscript: ~ + timeout: 300 # timeout for run a python or iverilog code; unit: second. + TBcheck: + rtl_num: 20 # the number of rtls used in the TB_check; will be ignored if the llmgen_rtls are provided in probset. + correct_max: 3 + discrim_mode: "col_full_wrong" + correct_mode: "naive" + rtl_compens_en: True # if True, when the half of the rtls contain syntax error, will generate more rtls to compensate. + rtl_compens_max_iter: 3 # the max iteration of generating rtls to compensate. + itermax: 10 # the max reboot times of the whole program; this reboot is trigered by TBcheck's next action + update_desc: False # if True, when reboot the program, will use the updated description of the task (from TBcheck) + save_compile: True # if True, save the compiling codes and files (codes in TBeval and TBcheck.discriminator); if False, not save. + save_finalcodes: True # if True, save the eventually generated Testbench codes (Verilog + Python); if False, not save. + error_interruption: False # if True, the program will stop when error occurs; Usually used in debugging. diff --git a/config/key_API.json b/config/key_API.json new file mode 100644 index 0000000..685ed6c --- /dev/null +++ b/config/key_API.json @@ -0,0 +1,5 @@ +{ + "OPENAI_API_KEY" : "", + "ANTHROPIC_API_KEY" : "", + "dashscope" : "" +} diff --git a/data/__init__.py b/data/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/data/__pycache__/__init__.cpython-312.pyc b/data/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a4007ca23d56a3f15230978ada57073b1754addb GIT binary patch literal 148 zcmX@j%ge<81m`qPWrFC(AOanHW&w&!XQ*V*Wb|9fP{ah}eFmxdWvri(pPQ;*m64d2 zuJ4>*RFs-r;*^?~oS`4$l%ARw?^sZvpORRTs2?AnnU`4-AFo$X`HRCQH$SB`C)KWq V6=(({5Ep|OADI~$8H<>KEC5%gBGdo? literal 0 HcmV?d00001 diff --git a/data/__pycache__/probset.cpython-312.pyc b/data/__pycache__/probset.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..cd90703e024d602450dfd36ada06bce02fc9b613 GIT binary patch literal 14781 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 Q{*tr2wFg9jtD> ") + # if task_id is None: + # task_id = input("Please enter task_id:\n>> ") + msg = iverilog_call(dir) + save_iv_runinfo(msg, dir) + print(msg) + +if __name__ == '__main__': + arg = len(sys.argv) + if arg == 2: + main(sys.argv[1]) + else: + dir_path = RUN_DIR if RUN_DIR.endswith(IC) else RUN_DIR + IC + main(RUN_DIR) diff --git a/loader_saver.py b/loader_saver.py new file mode 100644 index 0000000..a81e534 --- /dev/null +++ b/loader_saver.py @@ -0,0 +1,584 @@ +""" +Description : This module contains all functions related to loading and saving (except config loading). +Author : Ruidi Qiu (ruidi.qiu@tum.de) +Time : 2023/11/28 14:03:56 +LastEdited : 2024/8/23 12:37:25 +""" + +import os +import sys +import inspect +from config.config import Dict +import config as cfg +from config import Config +# import matplotlib.pyplot as plt +import yaml +import json +from io import StringIO +from loguru import logger as logurulogger +from utils.utils import print_time, Timer, str_list + +ORIGINAL_RUN_PATH = os.getcwd() + +########################## yaml/json utils ############################ +def load_yaml_dict(path: str): + with open(path, 'rb') as f: + yaml_dict = yaml.safe_load(f) + return yaml_dict + +def load_json_dict(path: str): + with open(path, 'r') as f: + json_dict = json.load(f) + return json_dict + +def save_dict_json_form(json_dict, path: str): + with open(path, 'w') as f: + json.dump(json_dict, f, indent=4) + +def load_txt(path: str): + with open(path, 'r') as f: + txt = f.read() + return txt + +# jsonl related: +def load_json_lines(path: str): + with open(path, 'r') as f: + data = [json.loads(line) for line in f] + return data + +def save_json_lines(data, path: str): + with open(path, 'w') as f: + for line in data: + json.dump(line, f) + f.write('\n') + +def find_line_jsonl(id_or_number, data): + """ + quickly find the line in data by task_id or task_number + """ + if isinstance(id_or_number, str): + for line in data: + if line["task_id"] == id_or_number: + return line + elif isinstance(id_or_number, int): + for line in data: + if line["task_number"] == id_or_number: + return line + +########################## set save path ############################ +def save_path_setting(config, save_type, custom_name=''): + '''support log/data/plot/postproc saving''' + SAVE_TYPE_DICT = { + 'log': config.save.log, + 'message': config.save.message, + 'tb': config.save.iverilog, + 'dir': config.save.pub + } + type_config = SAVE_TYPE_DICT[save_type] + run = config.run + pub = config.save.pub + if not save_type in SAVE_TYPE_DICT.keys(): + raise Exception('no such saving type named \"%s\"' % (save_type)) + # file name: + if pub.prefix is None: + unique_name = '%s'%(run.time) + else: + unique_name = '%s_%s'%(str(pub.prefix), run.time) + if custom_name != '': + custom_name = '%s_'%(custom_name) + file_name = custom_name + unique_name + # dir: + # if (pub.dir is None): + # save_dir = type_config.dir + # else: + # if pub.subdir not in ['', None] and not pub.subdir.endswith('/'): # in case of missing '/' + # pub.subdir = pub.subdir+'/' + # else: + # pub.subdir = pub.subdir + # save_dir = '%s%s%s/'%(pub.dir, pub.subdir, unique_name) + if pub.subdir not in ['', None] and not pub.subdir.endswith('/'): # in case of missing '/' + pub.subdir = pub.subdir+'/' + else: + pub.subdir = pub.subdir + save_dir = '%s%s%s/'%(pub.dir, pub.subdir, unique_name) + if save_type == 'dir': + return save_dir + if os.path.exists(save_dir) != True: #dir check + os.makedirs(save_dir) + # suffix: + suffix_dict = {'log': '.log', 'tb': '.v'}#, 'model': '.pt'} + if save_type == 'message': # leave the suffix to the user + suffix = '' + else: + suffix = suffix_dict[save_type] + # merge: + save_path = save_dir + file_name + suffix + return save_path + +def add_save_root_to(config): + """save root; ends with '/'""" + config.save.root = save_path_setting(config, 'dir') + return config + +############################# log save ############################### +class AutoLogger: + """singleton class, used as the logger in the project. supported by loguru.""" + _instance = None + _initialized = False + def __new__(cls, *args, **kwargs): + if cls._instance is None: + cls._instance = super(AutoLogger, cls).__new__(cls) + return cls._instance + + def __init__(self): + if not AutoLogger._initialized: + if Config._instance is not None: # make sure that the initialization of logger is after the config + AutoLogger._initialized = True + self.config = Config() + self.debug_en = self.config.save.log.debug_en + self.log_level = self.config.save.log.level + self.logger = logurulogger + # self.log_root_dir = "" + # self.runing_log_path = os.path.join(self.log_root_dir, 'running.log') + self.running_log_path = save_path_setting(self.config, 'log') + self._save_config() # now the log is always saved + self._init_levels() + self._init_handlers() + self.log_stream = None + self.running_log_id = None + self.temp_log_id = None + self.logline_prefix_list:list[str] = [] + + def __call__(self, string:str): + self.info(string) + + def _init_levels(self): + """initialize the customized log levels""" + self.logger.level("POSITIVE", no=22, color="") # #d7ffd7 in console + self.logger.level("NEGATIVE", no=23, color="") # #ffd7ff in console + self.logger.level("FAILED", no=26, color="") + + def _init_handlers(self): + """set the loggers for the project""" + self.logger.remove(0) # remove the original stderr handler (console handler) + ##### console logger: + console_level = "INFO" if not self.debug_en else "DEBUG" + self.logger.add(sys.stdout, level=console_level, format="{time:YYYY-MM-DD HH:mm:ss.S} | {level: <8} | {message}") # ... means colorize the text + ##### running logger: + self.running_log_id = self._set_handler(self.running_log_path, level=self.log_level) + + def _set_handler(self, path:str|StringIO, level:str=None, filter=None, format=None) -> int: + """ + - set one handler for the logger (see loguru documentation to know what is a handler) + - input: + - level: the lowest level of the log that will be saved for this handler + - filter: the customized filter (func/dict/str) to match what to be saved + - format: the format of each record + """ + if format is None: + format = "{time:YYYY-MM-DD HH:mm:ss} | {level: <8} | {message}" + # format = "{time: HH:mm:ss} | {level} | {message}" + format : str + paras = { + "sink": path, + "format": format, + "level": level, + "filter": filter + } + if isinstance(path, str): + paras["rotation"] = '500 MB' + paras["encoding"] = 'utf-8' + handler_id = self.logger.add(**paras) + return handler_id + + # methods for temporary logging + def set_temp_log(self): + """initialize a temporary log stream, if already exists, reset it first""" + if self.temp_log_id is not None: + self.reset_temp_log() + self.log_stream = StringIO() + self.temp_log_id = self._set_handler(self.log_stream, level=self.log_level) # same as the running logger + + def reset_temp_log(self): + """delete the temporary log and return the content""" + if self.temp_log_id is not None: + log_content = self.get_temp_log() + self.logger.remove(self.temp_log_id) + self.log_stream.close() + self.log_stream = None + self.temp_log_id = None + return log_content + else: + self.logger.warning("logger warning: no temp log to reset") + return "" + + def get_temp_log(self)->str: + """get the content of the temporary log""" + if self.log_stream is not None: + return self.log_stream.getvalue() + else: + return "" + + # methods for prefix of a line of log + def set_prefix(self, prefix:str): + """ + - set the prefix for the log line. the prefix will be stacked. + - for example, if you set prefix1, prefix2, prefix3, the log line will be like: [prefix1] (prefix2) (prefix3) log_content + """ + self.logline_prefix_list.append(prefix) + + def pop_prefix(self): + """pop the last prefix out of the prefix stack""" + if len(self.logline_prefix_list) > 0: + self.logline_prefix_list.pop() + else: + self.logger.warning("logger warning: no prefix to pop") + + def clear_prefix(self): + """clear all the prefixes""" + self.logline_prefix_list.clear() + + def show_prefix(self): + """show the current prefixes""" + self.logger.info(f"current prefixes: {self.logline_prefix}") + + def check_prefix(self, prefix:str): + """check if the prefix is in the prefix stack""" + return prefix in self.logline_prefix_list + + @property + def logline_prefix(self): + """ + - the prefix will be like: "[prefix1] (prefix2) (prefix3)", there should be [ ]/( ) around each prefix + - if there is no prefix, return " " + """ + first_bracker = "[]" + other_bracker = "[]" + if len(self.logline_prefix_list) > 0: + prefix_first = first_bracker[0] + self.logline_prefix_list[0] + first_bracker[-1] + " " + if len(self.logline_prefix_list) > 1: + prefix_other = " ".join([(other_bracker[0] + prefix + other_bracker[-1]) for prefix in self.logline_prefix_list[1:]]) + " " + else: + prefix_other = "" + return prefix_first + prefix_other + else: + return "" + + # methods for logging + def trace(self, string:str): + """ the most trival info, level value: 5""" + self.logger.trace(self.logline_prefix + string) + + def debug(self, string:str): + """ debug level log, some point info; level value: 10""" + string = string + " | " + self._get_caller_location() + self.logger.debug(self.logline_prefix + string) + + def info(self, string:str): + """info level log; level value: 20""" + self.logger.info(self.logline_prefix + string) + + def positive(self, string:str): + """positive level log; level value: 22""" + self.logger.log("POSITIVE", self.logline_prefix + string) + + def negative(self, string:str): + """negative level log; level value: 23""" + self.logger.log("NEGATIVE", self.logline_prefix + string) + + def success(self, string:str): + """success level log; level value: 25""" + self.logger.success(self.logline_prefix + string) + + def failed(self, string:str): + """failed level log; level value: 26""" + self.logger.log("FAILED", self.logline_prefix + string) + + def warning(self, string:str): + """warning level log; level value: 30""" + string = string + " | " + self._get_caller_location() + " | caller: " + self._get_caller_location(3) + self.logger.warning(self.logline_prefix + string) + + def error(self, string:str): + """error level log; level value: 40""" + string = string + " | " + self._get_caller_location() + " | caller: " + self._get_caller_location(3) + self.logger.error(self.logline_prefix + string) + + def critical(self, string:str): + """critical level log; level value: 50""" + string = string + " | " + self._get_caller_location() + " | caller: " + self._get_caller_location(3) + self.logger.critical(self.logline_prefix + string) + + def match_level(self, condition:bool, yeslevel:str, nolevel:str, string:str): + """will log the string with yeslevel if condition is True, else with nolevel""" + if condition: + getattr(self, yeslevel)(string) + else: + getattr(self, nolevel)(string) + + def assert_(self, condition:bool, string:str, level:str='critical'): + """ + - similar to the assert statement, but will save the log before raising the AssertionError + - you can customize the level of the log, default: critical; you should use lower case + """ + if not condition: + getattr(self, level)(string) + raise AssertionError(string) + + @staticmethod + def _get_caller_location(level:int=2): + caller_file = inspect.stack()[level].filename + caller_file = os.path.relpath(caller_file, ORIGINAL_RUN_PATH) + caller_func = inspect.stack()[level].function + caller_line = inspect.stack()[level].lineno + return f"location: {caller_file}, func: {caller_func}, line: {caller_line}" + + def _save_config(self): + with open(self.running_log_path, 'a') as file: + #notes: + if not self.config.save.log.notes is None: + file.write('%s\n\n' % (self.config.save.log.notes)) + #config information: + print_config(file, self.config) + + """ + loguru level table + level | value + TRACE | 5 + DEBUG | 10 + INFO | 20 + SUCCESS| 25 + WARNING| 30 + ERROR | 40 + CRITICAL| 50 + + my level table + level | value + POSITIVE| 22 + NEGATIVE| 23 + FAILED | 26 + """ + +class log_localprefix: + """ + usage as a context manager + + :: + + with log_localprefix('prefix'): + # log content + logger.info('log content') + + -> "[prefix] log content" + + + usage as a decorator + + :: + + @log_localprefix('prefix') + def func(): + logger.info('log content') + + -> "[prefix] log content + + """ + def __init__(self, prefix:str): + self.prefix = prefix + self.logger = AutoLogger() + + def __enter__(self): + self.logger.set_prefix(self.prefix) + + def __exit__(self, exc_type, exc_val, exc_tb): + self.logger.pop_prefix() + + def __call__(self, func): + def wrapper(*args, **kwargs): + with self: + return func(*args, **kwargs) + return wrapper + +def print_dict(file, input_dict, indent='', ind_style=' '): + if isinstance(input_dict, Dict) or isinstance(input_dict, dict): + dict_items = input_dict.items() + else: + raise TypeError("input type of func 'print_dict' is not Dict(obj) or dict") + for k, v in dict_items: + if not isinstance(v, Dict) and not isinstance(v, dict): + file.write('%s%s: %s\n' % (indent, k, v)) + else: + file.write('%s%s: \n' % (indent, k)) + next_indent = '%s %s'%(ind_style, indent) + print_dict(file, v, next_indent, ind_style) + +def print_config(file, config): + """ + split mode: `running info` + `custom` config + `default` config + iwantall mode: `custom` config + `merged` config + `default` config + merge mode: `merged` config + """ + mode = config.save.log.cfg_pmode + indent_style = ' ' + if mode == 'split': + # running information: (only split mode) + file.write('---------------running info---------------\n') + file.write('last version: %s\n' % (config.run.version)) + file.write('custom config file: %s\n' % (config.run.custom_path)) + file.write('starting time: %s\n' % (config.run.time)) + file.write('host name: %s\n' % (config.run.hostname)) + file.write('process ID: %s\n' % (config.run.pid)) + file.write('------------------------------------------\n') + if mode in ['split', 'iwantall']: + # custom config: + custom_cfg, default_cfg = cfg.load_split_config(custom_config_path=config.run.custom_path) + file.write('\n---------------custom config--------------\n') + if custom_cfg is None: + file.write('\nNo customized configuration\n') + else: + print_dict(file, custom_cfg, ind_style = indent_style) + file.write('------------------------------------------\n') + if mode in ['merge', 'iwantall']: + # merged config: + file.write('------config info (custom + default)------\n') + print_dict(file, config, ind_style = indent_style) + file.write('------------------------------------------\n') + if mode in ['split', 'iwantall']: + # default config: + file.write('\n--------------default config--------------\n') + print_dict(file, default_cfg, ind_style = indent_style) + file.write('------------------------------------------\n') + +def save_config(): + config = Config() + save_path = save_path_setting(config, 'log') + with open(save_path, 'a') as file: + #notes: + if config.save.log.notes is not None: + file.write('%s\n\n' % (config.save.log.notes)) + #config information: + print_config(file, config) + +def save_log_line(line, config): + # if config.save.log.en: + # save_path = save_path_setting(config, 'log') + # with open(save_path, 'a') as file: + # file.write('%s\n'%(line)) + autologger = AutoLogger() + autologger.info(line) + +def print_and_save(line, config): + # print(line) + # if config.save.log.en: + # save_log_line(line, config) + autologger = AutoLogger() + autologger.info(line) + + +############################# message/code save ############################# +def save_messages_to_txt(messages, save_path): + os.makedirs(os.path.dirname(save_path), exist_ok=True) + with open(save_path, 'a') as file: + for message in messages: + if "time" in message.keys(): + file.write('########## %s (%ss used) ##########\n%s\n\n' % (message['role'], message['time'], message['content'])) + else: + file.write('########## %s ##########\n%s\n\n' % (message['role'], message['content'])) + file.write('\n') + +def print_messages(messages): + # just like save_messages_to_txt + for message in messages: + if "time" in message.keys(): + print('########## %s (%ss used) ##########\n%s\n' % (message['role'], message['time'], message['content'])) + else: + print('########## %s ##########\n%s\n' % (message['role'], message['content'])) + +def save_messages_to_log(messages, config): + save_path = save_path_setting(config, 'log') + save_messages_to_txt(messages, save_path) + +def gpt_message_individual_save(messages, config, file_name = None, file_format = "json", silent = False): + save_path = save_path_setting(config, 'message') + # change the file name (xxx/xx.json->xxx/file_name.json) + if file_name is not None: + save_path = save_path.split('/') + save_path[-1] = file_name + "." + file_format + save_path = '/'.join(save_path) + if file_format == 'txt': + save_messages_to_txt(messages, save_path) + elif file_format == 'json': + save_dict_json_form(messages, save_path) + # print + if not silent: + print("\n") + if file_name is not None: + print("(file name: %s)"%(file_name)) + print('your conversation with ChatGPT has been successfully saved to "%s"\n' % (save_path)) + +# def save_TB_code(code_txt, task_id, config): +# """save the verilog testbench code to a .v file.""" +# iverilog_dir = config.save.iverilog.subdir +# save_path = save_path_setting(config, 'tb') +# save_path = save_path.split('/') +# # insert iverilog dir +# save_path.insert(-1, iverilog_dir) +# tb_name = task_id + '_tb.v' +# save_path[-1] = tb_name +# save_dir = '/'.join(save_path[:-1]) + '/' +# save_path = '/'.join(save_path) +# if os.path.exists(save_dir) != True: #dir check +# os.makedirs(save_dir) +# with open(save_path, 'a') as file: +# file.write(code_txt) +# print("\n") +# print('your testbench code has been successfully saved to "%s"\n' % (save_path)) +# return {'name': tb_name, 'dir': save_dir, 'path': save_path} + + +# will be discarded in the future +def save_code_iv(code_txt, task_id, code_type, config, iverilog_dir = None, silent = False): + """ + save the verilog TB/DUT code to a .v file. This func is for iverilog call. + #### input: + - code_txt: the verilog code in string format + - task_id: the task id of the problem + - code_type: 'TB' or 'DUT' + - config: the config object + - iverilog_dir: the directory to save the code. If None, use the default directory. + """ + assert code_type in ["TB", "DUT"], "code_type should be 'TB' or 'DUT'" + suffix_dict = {'TB': '_tb.v', 'DUT': '.v'} + if iverilog_dir is None: + iverilog_subdir = config.save.iverilog.subdir + save_path = save_path_setting(config, 'tb') + save_path = save_path.split('/') + # insert iverilog dir + save_path.insert(-1, iverilog_subdir) + else: + if not iverilog_dir.endswith('/'): + iverilog_dir += '/' + iverilog_path = iverilog_dir + "name.v" + save_path = iverilog_path.split('/') + code_name = task_id + suffix_dict[code_type] + save_path[-1] = code_name + save_dir = '/'.join(save_path[:-1]) + '/' + save_path = '/'.join(save_path) + os.makedirs(save_dir, exist_ok=True) + with open(save_path, 'a') as file: + file.write(code_txt) + if not silent: + print("\n") + print('your %s code has been successfully saved to "%s"\n' % (code_type, save_path)) + return {'name': code_name, 'dir': save_dir, 'path': save_path, 'code_type': code_type} + +def save_code(code, path): + os.makedirs(os.path.dirname(path), exist_ok=True) + with open(path, 'w') as file: + file.write(code) + +autologger = AutoLogger() + +############################# __main__ ################################ +if __name__ == "__main__": + None \ No newline at end of file diff --git a/main.py b/main.py new file mode 100644 index 0000000..6c3a376 --- /dev/null +++ b/main.py @@ -0,0 +1,98 @@ +""" +TBgen_App - 入口文件 + +用法: + python main.py -d -w -o -m + +示例: + python main.py -d dut.v -w description.txt --header "module example(...)" -m qwen-max +""" +import sys +import getopt +import os +from pathlib import Path + +# 添加项目路径 +PROJECT_ROOT = Path(__file__).parent +sys.path.insert(0, str(PROJECT_ROOT)) + +from run_tbgen import generate_tb + + +def main(): + try: + opts, args = getopt.getopt(sys.argv[1:], "h:d:w:o:m:", + ["help", "dut=", "desc=", "header=", "output=", "model="]) + except getopt.GetoptError as err: + print(err) + sys.exit(2) + + dut_path = None + desc_path = None + header = None + output = "./output" + model = "qwen-max" + + for opt, arg in opts: + if opt in ("-h", "--help"): + print("Usage: python main.py -d -w [options]") + print("") + print("Options:") + print(" -d, --dut DUT文件路径 (必需)") + print(" -w, --desc 描述文件路径 (必需)") + print(" --header Module header (必需)") + print(" -o, --output 输出目录 (默认: ./output)") + print(" -m, --model 使用的模型 (默认: qwen-max)") + print(" -h, --help 显示帮助信息") + sys.exit(0) + elif opt in ("-d", "--dut"): + dut_path = arg + elif opt in ("-w", "--desc"): + desc_path = arg + elif opt in ("--header"): + header = arg + elif opt in ("-o", "--output"): + output = arg + elif opt in ("-m", "--model"): + model = arg + + if not dut_path or not desc_path: + print("Error: -d (dut) and -w (desc) are required") + print("Use -h or --help for usage information") + sys.exit(2) + + if not header: + print("Error: --header is required") + print("Use -h or --help for usage information") + sys.exit(2) + + # 读取文件 + with open(dut_path, "r") as f: + dut_code = f.read() + with open(desc_path, "r") as f: + description = f.read() + + # 生成TB + task_id = Path(dut_path).stem + print(f"Generating TB for task: {task_id}") + print(f"Model: {model}") + print(f"Output directory: {output}") + + tb_path, result = generate_tb( + dut_code=dut_code, + description=description, + header=header, + task_id=task_id, + model=model, + output_dir=output + ) + + print(f"\nTB generated successfully: {tb_path}") + if result.get("cga_coverage"): + print(f"Coverage: {result['cga_coverage']}") + if result.get("full_pass"): + print(f"Full Pass: {result['full_pass']}") + + +if __name__ == "__main__": + main() diff --git a/output/example_mul/example_mul/1_TBgen/TBgen_codes/TBout.txt b/output/example_mul/example_mul/1_TBgen/TBgen_codes/TBout.txt new file mode 100644 index 0000000..9d7f2d8 --- /dev/null +++ b/output/example_mul/example_mul/1_TBgen/TBgen_codes/TBout.txt @@ -0,0 +1,30 @@ +scenario: 1, clk = 0, rst = 1, a = 0, b = 0, y = z +[check]scenario: 1, clk = 1, rst = 1, a = 0, b = 0, y = z +scenario: 2, clk = 1, rst = 0, a = 1, b = 1, y = z +[check]scenario: 2, clk = 1, rst = 0, a = 1, b = 1, y = z +scenario: 3, clk = 1, rst = 0, a = 0, b = 255, y = z +[check]scenario: 3, clk = 1, rst = 0, a = 0, b = 255, y = z +scenario: 4, clk = 1, rst = 0, a = 255, b = 0, y = z +[check]scenario: 4, clk = 1, rst = 0, a = 255, b = 0, y = z +scenario: 5, clk = 1, rst = 0, a = 255, b = 255, y = z +[check]scenario: 5, clk = 1, rst = 0, a = 255, b = 255, y = z +scenario: 6, clk = 1, rst = 0, a = 128, b = 128, y = z +[check]scenario: 6, clk = 1, rst = 0, a = 128, b = 128, y = z +scenario: 7, clk = 1, rst = 0, a = 1, b = 128, y = z +[check]scenario: 7, clk = 1, rst = 0, a = 1, b = 128, y = z +scenario: 8, clk = 1, rst = 0, a = 128, b = 1, y = z +[check]scenario: 8, clk = 1, rst = 0, a = 128, b = 1, y = z +scenario: 9, clk = 1, rst = 0, a = 10, b = 11, y = z +[check]scenario: 9, clk = 1, rst = 0, a = 10, b = 11, y = z +scenario: 10, clk = 1, rst = 0, a = 127, b = 127, y = z +[check]scenario: 10, clk = 1, rst = 0, a = 127, b = 127, y = z +scenario: 11, clk = 1, rst = 0, a = 15, b = 240, y = z +[check]scenario: 11, clk = 1, rst = 0, a = 15, b = 240, y = z +scenario: 12, clk = 1, rst = 0, a = 0, b = 0, y = z +[check]scenario: 12, clk = 1, rst = 0, a = 0, b = 0, y = z +scenario: 13, clk = 1, rst = 0, a = 15, b = 15, y = z +[check]scenario: 13, clk = 1, rst = 0, a = 15, b = 15, y = z +scenario: 14, clk = 1, rst = 0, a = 14, b = 14, y = z +[check]scenario: 14, clk = 1, rst = 0, a = 14, b = 14, y = z +scenario: 15, clk = 1, rst = 0, a = 13, b = 13, y = z +[check]scenario: 15, clk = 1, rst = 0, a = 13, b = 13, y = z diff --git a/output/example_mul/example_mul/1_TBgen/TBgen_codes/example_mul.v b/output/example_mul/example_mul/1_TBgen/TBgen_codes/example_mul.v new file mode 100644 index 0000000..0cf13b0 --- /dev/null +++ b/output/example_mul/example_mul/1_TBgen/TBgen_codes/example_mul.v @@ -0,0 +1,3 @@ +module example(input clk, input rst, input [7:0] a, input [7:0] b, output [15:0] y); + +endmodule diff --git a/output/example_mul/example_mul/1_TBgen/TBgen_codes/example_mul_tb.py b/output/example_mul/example_mul/1_TBgen/TBgen_codes/example_mul_tb.py new file mode 100644 index 0000000..34aa94a --- /dev/null +++ b/output/example_mul/example_mul/1_TBgen/TBgen_codes/example_mul_tb.py @@ -0,0 +1,82 @@ +class GoldenDUT: + def __init__(self): + self.a_reg = 0 + self.b_reg = 0 + self.y_reg = 0 + self.rst_reg = 1 + + def load(self, signal_vector): + self.a_reg = signal_vector['a'] & 0xFF + self.b_reg = signal_vector['b'] & 0xFF + self.rst_reg = signal_vector['rst'] + if self.rst_reg == 1: + self.a_reg = 0 + self.b_reg = 0 + self.y_reg = 0 + else: + self.y_reg = self.multiply_8bit(self.a_reg, self.b_reg) + + def check(self, signal_vector): + expected_y = self.multiply_8bit(self.a_reg, self.b_reg) + observed_y = signal_vector['y'] & 0xFFFF + if expected_y != observed_y: + print(f"Scenario: {signal_vector['scenario']}, expected: y=0x{expected_y:04X}, observed: y=0x{observed_y:04X}") + return False + return True + + def multiply_8bit(self, a, b): + a = a & 0xFF + b = b & 0xFF + product = a * b + product = product & 0xFFFF + return product + +def check_dut(vectors_in): + golden_dut = GoldenDUT() + failed_scenarios = [] + for vector in vectors_in: + if vector["check_en"]: + check_pass = golden_dut.check(vector) + if check_pass: + print(f"Passed; vector: {vector}") + else: + print(f"Failed; vector: {vector}") + failed_scenarios.append(vector["scenario"]) + golden_dut.load(vector) + return failed_scenarios + +def SignalTxt_to_dictlist(txt:str): + signals = [] + lines = txt.strip().split("\n") + for line in lines: + signal = {} + if line.startswith("[check]"): + signal["check_en"] = True + line = line[7:] + elif line.startswith("scenario"): + signal["check_en"] = False + else: + continue + line = line.strip().split(", ") + for item in line: + if "scenario" in item: + item = item.split(": ") + signal["scenario"] = item[1].replace(" ", "") + else: + item = item.split(" = ") + key = item[0] + value = item[1] + if ("x" not in value) and ("X" not in value) and ("z" not in value): + signal[key] = int(value) + else: + if ("x" in value) or ("X" in value): + signal[key] = 0 # used to be "x" + else: + signal[key] = 0 # used to be "z" + signals.append(signal) + return signals +with open("TBout.txt", "r") as f: + txt = f.read() +vectors_in = SignalTxt_to_dictlist(txt) +tb_pass = check_dut(vectors_in) +print(tb_pass) diff --git a/output/example_mul/example_mul/1_TBgen/TBgen_codes/example_mul_tb.v b/output/example_mul/example_mul/1_TBgen/TBgen_codes/example_mul_tb.v new file mode 100644 index 0000000..95e6081 --- /dev/null +++ b/output/example_mul/example_mul/1_TBgen/TBgen_codes/example_mul_tb.v @@ -0,0 +1,138 @@ +`timescale 1ns / 1ps +module testbench; +reg clk; +reg rst; +reg [7:0] a; +reg [7:0] b; +wire [15:0] y; + +integer file, scenario; +// DUT instantiation +example DUT ( + .clk(clk), + .rst(rst), + .a(a), + .b(b), + .y(y) +); +// Clock generation +initial begin + clk = 0; + forever #5 clk = ~clk; +end + +initial begin + file = $fopen("TBout.txt", "w"); +end +// Scenario Based Test +initial begin + // scenario 1 + scenario = 1; + rst = 1; a = 8'b0000_0000; b = 8'b0000_0000; + $fdisplay(file, "scenario: %d, clk = %d, rst = %d, a = %d, b = %d, y = %d", scenario, clk, rst, a, b, y); + #10; + $fdisplay(file, "[check]scenario: %d, clk = %d, rst = %d, a = %d, b = %d, y = %d", scenario, clk, rst, a, b, y); #10; + + // scenario 2 + scenario = 2; + rst = 0; a = 8'b0000_0001; b = 8'b0000_0001; + $fdisplay(file, "scenario: %d, clk = %d, rst = %d, a = %d, b = %d, y = %d", scenario, clk, rst, a, b, y); + #10; + $fdisplay(file, "[check]scenario: %d, clk = %d, rst = %d, a = %d, b = %d, y = %d", scenario, clk, rst, a, b, y); #10; + + // scenario 3 + scenario = 3; + a = 8'b0000_0000; b = 8'b1111_1111; + $fdisplay(file, "scenario: %d, clk = %d, rst = %d, a = %d, b = %d, y = %d", scenario, clk, rst, a, b, y); + #10; + $fdisplay(file, "[check]scenario: %d, clk = %d, rst = %d, a = %d, b = %d, y = %d", scenario, clk, rst, a, b, y); #10; + + // scenario 4 + scenario = 4; + a = 8'b1111_1111; b = 8'b0000_0000; + $fdisplay(file, "scenario: %d, clk = %d, rst = %d, a = %d, b = %d, y = %d", scenario, clk, rst, a, b, y); + #10; + $fdisplay(file, "[check]scenario: %d, clk = %d, rst = %d, a = %d, b = %d, y = %d", scenario, clk, rst, a, b, y); #10; + + // scenario 5 + scenario = 5; + a = 8'b1111_1111; b = 8'b1111_1111; + $fdisplay(file, "scenario: %d, clk = %d, rst = %d, a = %d, b = %d, y = %d", scenario, clk, rst, a, b, y); + #10; + $fdisplay(file, "[check]scenario: %d, clk = %d, rst = %d, a = %d, b = %d, y = %d", scenario, clk, rst, a, b, y); #10; + + // scenario 6 + scenario = 6; + a = 8'b1000_0000; b = 8'b1000_0000; + $fdisplay(file, "scenario: %d, clk = %d, rst = %d, a = %d, b = %d, y = %d", scenario, clk, rst, a, b, y); + #10; + $fdisplay(file, "[check]scenario: %d, clk = %d, rst = %d, a = %d, b = %d, y = %d", scenario, clk, rst, a, b, y); #10; + + // scenario 7 + scenario = 7; + a = 8'b0000_0001; b = 8'b1000_0000; + $fdisplay(file, "scenario: %d, clk = %d, rst = %d, a = %d, b = %d, y = %d", scenario, clk, rst, a, b, y); + #10; + $fdisplay(file, "[check]scenario: %d, clk = %d, rst = %d, a = %d, b = %d, y = %d", scenario, clk, rst, a, b, y); #10; + + // scenario 8 + scenario = 8; + a = 8'b1000_0000; b = 8'b0000_0001; + $fdisplay(file, "scenario: %d, clk = %d, rst = %d, a = %d, b = %d, y = %d", scenario, clk, rst, a, b, y); + #10; + $fdisplay(file, "[check]scenario: %d, clk = %d, rst = %d, a = %d, b = %d, y = %d", scenario, clk, rst, a, b, y); #10; + + // scenario 9 + scenario = 9; + a = 8'b0000_1010; b = 8'b0000_1011; + $fdisplay(file, "scenario: %d, clk = %d, rst = %d, a = %d, b = %d, y = %d", scenario, clk, rst, a, b, y); + #10; + $fdisplay(file, "[check]scenario: %d, clk = %d, rst = %d, a = %d, b = %d, y = %d", scenario, clk, rst, a, b, y); #10; + + // scenario 10 + scenario = 10; + a = 8'b0111_1111; b = 8'b0111_1111; + $fdisplay(file, "scenario: %d, clk = %d, rst = %d, a = %d, b = %d, y = %d", scenario, clk, rst, a, b, y); + #10; + $fdisplay(file, "[check]scenario: %d, clk = %d, rst = %d, a = %d, b = %d, y = %d", scenario, clk, rst, a, b, y); #10; + + // scenario 11 + scenario = 11; + a = 8'b0000_1111; b = 8'b1111_0000; + $fdisplay(file, "scenario: %d, clk = %d, rst = %d, a = %d, b = %d, y = %d", scenario, clk, rst, a, b, y); + #10; + $fdisplay(file, "[check]scenario: %d, clk = %d, rst = %d, a = %d, b = %d, y = %d", scenario, clk, rst, a, b, y); #10; + + // scenario 12 + scenario = 12; + a = 8'b0000_0000; b = 8'b0000_0000; + $fdisplay(file, "scenario: %d, clk = %d, rst = %d, a = %d, b = %d, y = %d", scenario, clk, rst, a, b, y); + #10; + $fdisplay(file, "[check]scenario: %d, clk = %d, rst = %d, a = %d, b = %d, y = %d", scenario, clk, rst, a, b, y); #10; + + // scenario 13 + scenario = 13; + a = 8'b0000_1111; b = 8'b0000_1111; + $fdisplay(file, "scenario: %d, clk = %d, rst = %d, a = %d, b = %d, y = %d", scenario, clk, rst, a, b, y); + #10; + $fdisplay(file, "[check]scenario: %d, clk = %d, rst = %d, a = %d, b = %d, y = %d", scenario, clk, rst, a, b, y); #10; + + // scenario 14 + scenario = 14; + a = 8'b0000_1110; b = 8'b0000_1110; + $fdisplay(file, "scenario: %d, clk = %d, rst = %d, a = %d, b = %d, y = %d", scenario, clk, rst, a, b, y); + #10; + $fdisplay(file, "[check]scenario: %d, clk = %d, rst = %d, a = %d, b = %d, y = %d", scenario, clk, rst, a, b, y); #10; + + // scenario 15 + scenario = 15; + a = 8'b0000_1101; b = 8'b0000_1101; + $fdisplay(file, "scenario: %d, clk = %d, rst = %d, a = %d, b = %d, y = %d", scenario, clk, rst, a, b, y); + #10; + $fdisplay(file, "[check]scenario: %d, clk = %d, rst = %d, a = %d, b = %d, y = %d", scenario, clk, rst, a, b, y); + + $fclose(file); + $finish; +end + +endmodule diff --git a/output/example_mul/example_mul/1_TBgen/TBgen_codes/run.vvp b/output/example_mul/example_mul/1_TBgen/TBgen_codes/run.vvp new file mode 100644 index 0000000..23c5261 --- /dev/null +++ b/output/example_mul/example_mul/1_TBgen/TBgen_codes/run.vvp @@ -0,0 +1,218 @@ +#! /usr/bin/vvp +:ivl_version "12.0 (stable)"; +:ivl_delay_selection "TYPICAL"; +:vpi_time_precision - 12; +:vpi_module "/usr/lib/x86_64-linux-gnu/ivl/system.vpi"; +:vpi_module "/usr/lib/x86_64-linux-gnu/ivl/vhdl_sys.vpi"; +:vpi_module "/usr/lib/x86_64-linux-gnu/ivl/vhdl_textio.vpi"; +:vpi_module "/usr/lib/x86_64-linux-gnu/ivl/v2005_math.vpi"; +:vpi_module "/usr/lib/x86_64-linux-gnu/ivl/va_math.vpi"; +:vpi_module "/usr/lib/x86_64-linux-gnu/ivl/v2009.vpi"; +S_0x5f1b861d6b90 .scope package, "$unit" "$unit" 2 1; + .timescale 0 0; +S_0x5f1b861b9000 .scope module, "testbench" "testbench" 3 2; + .timescale -9 -12; +v0x5f1b861f7200_0 .var "a", 7 0; +v0x5f1b861f72e0_0 .var "b", 7 0; +v0x5f1b861f73b0_0 .var "clk", 0 0; +v0x5f1b861f74b0_0 .var/i "file", 31 0; +v0x5f1b861f7550_0 .var "rst", 0 0; +v0x5f1b861f7640_0 .var/i "scenario", 31 0; +o0x70ab3a8a60d8 .functor BUFZ 16, C4; HiZ drive +v0x5f1b861f76e0_0 .net "y", 15 0, o0x70ab3a8a60d8; 0 drivers +S_0x5f1b861b9190 .scope module, "DUT" "example" 3 11, 4 1 0, S_0x5f1b861b9000; + .timescale 0 0; + .port_info 0 /INPUT 1 "clk"; + .port_info 1 /INPUT 1 "rst"; + .port_info 2 /INPUT 8 "a"; + .port_info 3 /INPUT 8 "b"; + .port_info 4 /OUTPUT 16 "y"; +v0x5f1b861d47e0_0 .net "a", 7 0, v0x5f1b861f7200_0; 1 drivers +v0x5f1b861f6dc0_0 .net "b", 7 0, v0x5f1b861f72e0_0; 1 drivers +v0x5f1b861f6ea0_0 .net "clk", 0 0, v0x5f1b861f73b0_0; 1 drivers +v0x5f1b861f6f70_0 .net "rst", 0 0, v0x5f1b861f7550_0; 1 drivers +v0x5f1b861f7030_0 .net "y", 15 0, o0x70ab3a8a60d8; alias, 0 drivers + .scope S_0x5f1b861b9000; +T_0 ; + %pushi/vec4 0, 0, 1; + %store/vec4 v0x5f1b861f73b0_0, 0, 1; +T_0.0 ; + %delay 5000, 0; + %load/vec4 v0x5f1b861f73b0_0; + %inv; + %store/vec4 v0x5f1b861f73b0_0, 0, 1; + %jmp T_0.0; + %end; + .thread T_0; + .scope S_0x5f1b861b9000; +T_1 ; + %vpi_func 3 25 "$fopen" 32, "TBout.txt", "w" {0 0 0}; + %store/vec4 v0x5f1b861f74b0_0, 0, 32; + %end; + .thread T_1; + .scope S_0x5f1b861b9000; +T_2 ; + %pushi/vec4 1, 0, 32; + %store/vec4 v0x5f1b861f7640_0, 0, 32; + %pushi/vec4 1, 0, 1; + %store/vec4 v0x5f1b861f7550_0, 0, 1; + %pushi/vec4 0, 0, 8; + %store/vec4 v0x5f1b861f7200_0, 0, 8; + %pushi/vec4 0, 0, 8; + %store/vec4 v0x5f1b861f72e0_0, 0, 8; + %vpi_call/w 3 32 "$fdisplay", v0x5f1b861f74b0_0, "scenario: %d, clk = %d, rst = %d, a = %d, b = %d, y = %d", v0x5f1b861f7640_0, v0x5f1b861f73b0_0, v0x5f1b861f7550_0, v0x5f1b861f7200_0, v0x5f1b861f72e0_0, v0x5f1b861f76e0_0 {0 0 0}; + %delay 10000, 0; + %vpi_call/w 3 34 "$fdisplay", v0x5f1b861f74b0_0, "[check]scenario: %d, clk = %d, rst = %d, a = %d, b = %d, y = %d", v0x5f1b861f7640_0, v0x5f1b861f73b0_0, v0x5f1b861f7550_0, v0x5f1b861f7200_0, v0x5f1b861f72e0_0, v0x5f1b861f76e0_0 {0 0 0}; + %delay 10000, 0; + %pushi/vec4 2, 0, 32; + %store/vec4 v0x5f1b861f7640_0, 0, 32; + %pushi/vec4 0, 0, 1; + %store/vec4 v0x5f1b861f7550_0, 0, 1; + %pushi/vec4 1, 0, 8; + %store/vec4 v0x5f1b861f7200_0, 0, 8; + %pushi/vec4 1, 0, 8; + %store/vec4 v0x5f1b861f72e0_0, 0, 8; + %vpi_call/w 3 39 "$fdisplay", v0x5f1b861f74b0_0, "scenario: %d, clk = %d, rst = %d, a = %d, b = %d, y = %d", v0x5f1b861f7640_0, v0x5f1b861f73b0_0, v0x5f1b861f7550_0, v0x5f1b861f7200_0, v0x5f1b861f72e0_0, v0x5f1b861f76e0_0 {0 0 0}; + %delay 10000, 0; + %vpi_call/w 3 41 "$fdisplay", v0x5f1b861f74b0_0, "[check]scenario: %d, clk = %d, rst = %d, a = %d, b = %d, y = %d", v0x5f1b861f7640_0, v0x5f1b861f73b0_0, v0x5f1b861f7550_0, v0x5f1b861f7200_0, v0x5f1b861f72e0_0, v0x5f1b861f76e0_0 {0 0 0}; + %delay 10000, 0; + %pushi/vec4 3, 0, 32; + %store/vec4 v0x5f1b861f7640_0, 0, 32; + %pushi/vec4 0, 0, 8; + %store/vec4 v0x5f1b861f7200_0, 0, 8; + %pushi/vec4 255, 0, 8; + %store/vec4 v0x5f1b861f72e0_0, 0, 8; + %vpi_call/w 3 46 "$fdisplay", v0x5f1b861f74b0_0, "scenario: %d, clk = %d, rst = %d, a = %d, b = %d, y = %d", v0x5f1b861f7640_0, v0x5f1b861f73b0_0, v0x5f1b861f7550_0, v0x5f1b861f7200_0, v0x5f1b861f72e0_0, v0x5f1b861f76e0_0 {0 0 0}; + %delay 10000, 0; + %vpi_call/w 3 48 "$fdisplay", v0x5f1b861f74b0_0, "[check]scenario: %d, clk = %d, rst = %d, a = %d, b = %d, y = %d", v0x5f1b861f7640_0, v0x5f1b861f73b0_0, v0x5f1b861f7550_0, v0x5f1b861f7200_0, v0x5f1b861f72e0_0, v0x5f1b861f76e0_0 {0 0 0}; + %delay 10000, 0; + %pushi/vec4 4, 0, 32; + %store/vec4 v0x5f1b861f7640_0, 0, 32; + %pushi/vec4 255, 0, 8; + %store/vec4 v0x5f1b861f7200_0, 0, 8; + %pushi/vec4 0, 0, 8; + %store/vec4 v0x5f1b861f72e0_0, 0, 8; + %vpi_call/w 3 53 "$fdisplay", v0x5f1b861f74b0_0, "scenario: %d, clk = %d, rst = %d, a = %d, b = %d, y = %d", v0x5f1b861f7640_0, v0x5f1b861f73b0_0, v0x5f1b861f7550_0, v0x5f1b861f7200_0, v0x5f1b861f72e0_0, v0x5f1b861f76e0_0 {0 0 0}; + %delay 10000, 0; + %vpi_call/w 3 55 "$fdisplay", v0x5f1b861f74b0_0, "[check]scenario: %d, clk = %d, rst = %d, a = %d, b = %d, y = %d", v0x5f1b861f7640_0, v0x5f1b861f73b0_0, v0x5f1b861f7550_0, v0x5f1b861f7200_0, v0x5f1b861f72e0_0, v0x5f1b861f76e0_0 {0 0 0}; + %delay 10000, 0; + %pushi/vec4 5, 0, 32; + %store/vec4 v0x5f1b861f7640_0, 0, 32; + %pushi/vec4 255, 0, 8; + %store/vec4 v0x5f1b861f7200_0, 0, 8; + %pushi/vec4 255, 0, 8; + %store/vec4 v0x5f1b861f72e0_0, 0, 8; + %vpi_call/w 3 60 "$fdisplay", v0x5f1b861f74b0_0, "scenario: %d, clk = %d, rst = %d, a = %d, b = %d, y = %d", v0x5f1b861f7640_0, v0x5f1b861f73b0_0, v0x5f1b861f7550_0, v0x5f1b861f7200_0, v0x5f1b861f72e0_0, v0x5f1b861f76e0_0 {0 0 0}; + %delay 10000, 0; + %vpi_call/w 3 62 "$fdisplay", v0x5f1b861f74b0_0, "[check]scenario: %d, clk = %d, rst = %d, a = %d, b = %d, y = %d", v0x5f1b861f7640_0, v0x5f1b861f73b0_0, v0x5f1b861f7550_0, v0x5f1b861f7200_0, v0x5f1b861f72e0_0, v0x5f1b861f76e0_0 {0 0 0}; + %delay 10000, 0; + %pushi/vec4 6, 0, 32; + %store/vec4 v0x5f1b861f7640_0, 0, 32; + %pushi/vec4 128, 0, 8; + %store/vec4 v0x5f1b861f7200_0, 0, 8; + %pushi/vec4 128, 0, 8; + %store/vec4 v0x5f1b861f72e0_0, 0, 8; + %vpi_call/w 3 67 "$fdisplay", v0x5f1b861f74b0_0, "scenario: %d, clk = %d, rst = %d, a = %d, b = %d, y = %d", v0x5f1b861f7640_0, v0x5f1b861f73b0_0, v0x5f1b861f7550_0, v0x5f1b861f7200_0, v0x5f1b861f72e0_0, v0x5f1b861f76e0_0 {0 0 0}; + %delay 10000, 0; + %vpi_call/w 3 69 "$fdisplay", v0x5f1b861f74b0_0, "[check]scenario: %d, clk = %d, rst = %d, a = %d, b = %d, y = %d", v0x5f1b861f7640_0, v0x5f1b861f73b0_0, v0x5f1b861f7550_0, v0x5f1b861f7200_0, v0x5f1b861f72e0_0, v0x5f1b861f76e0_0 {0 0 0}; + %delay 10000, 0; + %pushi/vec4 7, 0, 32; + %store/vec4 v0x5f1b861f7640_0, 0, 32; + %pushi/vec4 1, 0, 8; + %store/vec4 v0x5f1b861f7200_0, 0, 8; + %pushi/vec4 128, 0, 8; + %store/vec4 v0x5f1b861f72e0_0, 0, 8; + %vpi_call/w 3 74 "$fdisplay", v0x5f1b861f74b0_0, "scenario: %d, clk = %d, rst = %d, a = %d, b = %d, y = %d", v0x5f1b861f7640_0, v0x5f1b861f73b0_0, v0x5f1b861f7550_0, v0x5f1b861f7200_0, v0x5f1b861f72e0_0, v0x5f1b861f76e0_0 {0 0 0}; + %delay 10000, 0; + %vpi_call/w 3 76 "$fdisplay", v0x5f1b861f74b0_0, "[check]scenario: %d, clk = %d, rst = %d, a = %d, b = %d, y = %d", v0x5f1b861f7640_0, v0x5f1b861f73b0_0, v0x5f1b861f7550_0, v0x5f1b861f7200_0, v0x5f1b861f72e0_0, v0x5f1b861f76e0_0 {0 0 0}; + %delay 10000, 0; + %pushi/vec4 8, 0, 32; + %store/vec4 v0x5f1b861f7640_0, 0, 32; + %pushi/vec4 128, 0, 8; + %store/vec4 v0x5f1b861f7200_0, 0, 8; + %pushi/vec4 1, 0, 8; + %store/vec4 v0x5f1b861f72e0_0, 0, 8; + %vpi_call/w 3 81 "$fdisplay", v0x5f1b861f74b0_0, "scenario: %d, clk = %d, rst = %d, a = %d, b = %d, y = %d", v0x5f1b861f7640_0, v0x5f1b861f73b0_0, v0x5f1b861f7550_0, v0x5f1b861f7200_0, v0x5f1b861f72e0_0, v0x5f1b861f76e0_0 {0 0 0}; + %delay 10000, 0; + %vpi_call/w 3 83 "$fdisplay", v0x5f1b861f74b0_0, "[check]scenario: %d, clk = %d, rst = %d, a = %d, b = %d, y = %d", v0x5f1b861f7640_0, v0x5f1b861f73b0_0, v0x5f1b861f7550_0, v0x5f1b861f7200_0, v0x5f1b861f72e0_0, v0x5f1b861f76e0_0 {0 0 0}; + %delay 10000, 0; + %pushi/vec4 9, 0, 32; + %store/vec4 v0x5f1b861f7640_0, 0, 32; + %pushi/vec4 10, 0, 8; + %store/vec4 v0x5f1b861f7200_0, 0, 8; + %pushi/vec4 11, 0, 8; + %store/vec4 v0x5f1b861f72e0_0, 0, 8; + %vpi_call/w 3 88 "$fdisplay", v0x5f1b861f74b0_0, "scenario: %d, clk = %d, rst = %d, a = %d, b = %d, y = %d", v0x5f1b861f7640_0, v0x5f1b861f73b0_0, v0x5f1b861f7550_0, v0x5f1b861f7200_0, v0x5f1b861f72e0_0, v0x5f1b861f76e0_0 {0 0 0}; + %delay 10000, 0; + %vpi_call/w 3 90 "$fdisplay", v0x5f1b861f74b0_0, "[check]scenario: %d, clk = %d, rst = %d, a = %d, b = %d, y = %d", v0x5f1b861f7640_0, v0x5f1b861f73b0_0, v0x5f1b861f7550_0, v0x5f1b861f7200_0, v0x5f1b861f72e0_0, v0x5f1b861f76e0_0 {0 0 0}; + %delay 10000, 0; + %pushi/vec4 10, 0, 32; + %store/vec4 v0x5f1b861f7640_0, 0, 32; + %pushi/vec4 127, 0, 8; + %store/vec4 v0x5f1b861f7200_0, 0, 8; + %pushi/vec4 127, 0, 8; + %store/vec4 v0x5f1b861f72e0_0, 0, 8; + %vpi_call/w 3 95 "$fdisplay", v0x5f1b861f74b0_0, "scenario: %d, clk = %d, rst = %d, a = %d, b = %d, y = %d", v0x5f1b861f7640_0, v0x5f1b861f73b0_0, v0x5f1b861f7550_0, v0x5f1b861f7200_0, v0x5f1b861f72e0_0, v0x5f1b861f76e0_0 {0 0 0}; + %delay 10000, 0; + %vpi_call/w 3 97 "$fdisplay", v0x5f1b861f74b0_0, "[check]scenario: %d, clk = %d, rst = %d, a = %d, b = %d, y = %d", v0x5f1b861f7640_0, v0x5f1b861f73b0_0, v0x5f1b861f7550_0, v0x5f1b861f7200_0, v0x5f1b861f72e0_0, v0x5f1b861f76e0_0 {0 0 0}; + %delay 10000, 0; + %pushi/vec4 11, 0, 32; + %store/vec4 v0x5f1b861f7640_0, 0, 32; + %pushi/vec4 15, 0, 8; + %store/vec4 v0x5f1b861f7200_0, 0, 8; + %pushi/vec4 240, 0, 8; + %store/vec4 v0x5f1b861f72e0_0, 0, 8; + %vpi_call/w 3 102 "$fdisplay", v0x5f1b861f74b0_0, "scenario: %d, clk = %d, rst = %d, a = %d, b = %d, y = %d", v0x5f1b861f7640_0, v0x5f1b861f73b0_0, v0x5f1b861f7550_0, v0x5f1b861f7200_0, v0x5f1b861f72e0_0, v0x5f1b861f76e0_0 {0 0 0}; + %delay 10000, 0; + %vpi_call/w 3 104 "$fdisplay", v0x5f1b861f74b0_0, "[check]scenario: %d, clk = %d, rst = %d, a = %d, b = %d, y = %d", v0x5f1b861f7640_0, v0x5f1b861f73b0_0, v0x5f1b861f7550_0, v0x5f1b861f7200_0, v0x5f1b861f72e0_0, v0x5f1b861f76e0_0 {0 0 0}; + %delay 10000, 0; + %pushi/vec4 12, 0, 32; + %store/vec4 v0x5f1b861f7640_0, 0, 32; + %pushi/vec4 0, 0, 8; + %store/vec4 v0x5f1b861f7200_0, 0, 8; + %pushi/vec4 0, 0, 8; + %store/vec4 v0x5f1b861f72e0_0, 0, 8; + %vpi_call/w 3 109 "$fdisplay", v0x5f1b861f74b0_0, "scenario: %d, clk = %d, rst = %d, a = %d, b = %d, y = %d", v0x5f1b861f7640_0, v0x5f1b861f73b0_0, v0x5f1b861f7550_0, v0x5f1b861f7200_0, v0x5f1b861f72e0_0, v0x5f1b861f76e0_0 {0 0 0}; + %delay 10000, 0; + %vpi_call/w 3 111 "$fdisplay", v0x5f1b861f74b0_0, "[check]scenario: %d, clk = %d, rst = %d, a = %d, b = %d, y = %d", v0x5f1b861f7640_0, v0x5f1b861f73b0_0, v0x5f1b861f7550_0, v0x5f1b861f7200_0, v0x5f1b861f72e0_0, v0x5f1b861f76e0_0 {0 0 0}; + %delay 10000, 0; + %pushi/vec4 13, 0, 32; + %store/vec4 v0x5f1b861f7640_0, 0, 32; + %pushi/vec4 15, 0, 8; + %store/vec4 v0x5f1b861f7200_0, 0, 8; + %pushi/vec4 15, 0, 8; + %store/vec4 v0x5f1b861f72e0_0, 0, 8; + %vpi_call/w 3 116 "$fdisplay", v0x5f1b861f74b0_0, "scenario: %d, clk = %d, rst = %d, a = %d, b = %d, y = %d", v0x5f1b861f7640_0, v0x5f1b861f73b0_0, v0x5f1b861f7550_0, v0x5f1b861f7200_0, v0x5f1b861f72e0_0, v0x5f1b861f76e0_0 {0 0 0}; + %delay 10000, 0; + %vpi_call/w 3 118 "$fdisplay", v0x5f1b861f74b0_0, "[check]scenario: %d, clk = %d, rst = %d, a = %d, b = %d, y = %d", v0x5f1b861f7640_0, v0x5f1b861f73b0_0, v0x5f1b861f7550_0, v0x5f1b861f7200_0, v0x5f1b861f72e0_0, v0x5f1b861f76e0_0 {0 0 0}; + %delay 10000, 0; + %pushi/vec4 14, 0, 32; + %store/vec4 v0x5f1b861f7640_0, 0, 32; + %pushi/vec4 14, 0, 8; + %store/vec4 v0x5f1b861f7200_0, 0, 8; + %pushi/vec4 14, 0, 8; + %store/vec4 v0x5f1b861f72e0_0, 0, 8; + %vpi_call/w 3 123 "$fdisplay", v0x5f1b861f74b0_0, "scenario: %d, clk = %d, rst = %d, a = %d, b = %d, y = %d", v0x5f1b861f7640_0, v0x5f1b861f73b0_0, v0x5f1b861f7550_0, v0x5f1b861f7200_0, v0x5f1b861f72e0_0, v0x5f1b861f76e0_0 {0 0 0}; + %delay 10000, 0; + %vpi_call/w 3 125 "$fdisplay", v0x5f1b861f74b0_0, "[check]scenario: %d, clk = %d, rst = %d, a = %d, b = %d, y = %d", v0x5f1b861f7640_0, v0x5f1b861f73b0_0, v0x5f1b861f7550_0, v0x5f1b861f7200_0, v0x5f1b861f72e0_0, v0x5f1b861f76e0_0 {0 0 0}; + %delay 10000, 0; + %pushi/vec4 15, 0, 32; + %store/vec4 v0x5f1b861f7640_0, 0, 32; + %pushi/vec4 13, 0, 8; + %store/vec4 v0x5f1b861f7200_0, 0, 8; + %pushi/vec4 13, 0, 8; + %store/vec4 v0x5f1b861f72e0_0, 0, 8; + %vpi_call/w 3 130 "$fdisplay", v0x5f1b861f74b0_0, "scenario: %d, clk = %d, rst = %d, a = %d, b = %d, y = %d", v0x5f1b861f7640_0, v0x5f1b861f73b0_0, v0x5f1b861f7550_0, v0x5f1b861f7200_0, v0x5f1b861f72e0_0, v0x5f1b861f76e0_0 {0 0 0}; + %delay 10000, 0; + %vpi_call/w 3 132 "$fdisplay", v0x5f1b861f74b0_0, "[check]scenario: %d, clk = %d, rst = %d, a = %d, b = %d, y = %d", v0x5f1b861f7640_0, v0x5f1b861f73b0_0, v0x5f1b861f7550_0, v0x5f1b861f7200_0, v0x5f1b861f72e0_0, v0x5f1b861f76e0_0 {0 0 0}; + %vpi_call/w 3 134 "$fclose", v0x5f1b861f74b0_0 {0 0 0}; + %vpi_call/w 3 135 "$finish" {0 0 0}; + %end; + .thread T_2; +# The file index is used to find the file name in the following table. +:file_names 5; + "N/A"; + ""; + "-"; + "example_mul_tb.v"; + "example_mul.v"; diff --git a/output/example_mul/example_mul/1_TBgen/TBgen_codes/run_info.txt b/output/example_mul/example_mul/1_TBgen/TBgen_codes/run_info.txt new file mode 100644 index 0000000..e5749f8 --- /dev/null +++ b/output/example_mul/example_mul/1_TBgen/TBgen_codes/run_info.txt @@ -0,0 +1,15 @@ +iverilog simulation passed! + +iverilog cmd 1: +/usr/bin/iverilog -g2012 -o run.vvp example_mul.v example_mul_tb.v +iverilog cmd 1 output: + +iverilog cmd 1 error: + +iverilog cmd 2: +/usr/bin/vvp run.vvp +iverilog cmd 2 output: +example_mul_tb.v:135: $finish called at 290000 (1ps) + +iverilog cmd 2 error: + diff --git a/output/example_mul/example_mul/1_TBgen/TBgen_codes/run_info_py.txt b/output/example_mul/example_mul/1_TBgen/TBgen_codes/run_info_py.txt new file mode 100644 index 0000000..3358410 --- /dev/null +++ b/output/example_mul/example_mul/1_TBgen/TBgen_codes/run_info_py.txt @@ -0,0 +1,35 @@ +python compilation passed! + +###output: +Passed; vector: {'check_en': True, 'scenario': '1', 'clk': 1, 'rst': 1, 'a': 0, 'b': 0, 'y': 0} +Scenario: 2, expected: y=0x0001, observed: y=0x0000 +Failed; vector: {'check_en': True, 'scenario': '2', 'clk': 1, 'rst': 0, 'a': 1, 'b': 1, 'y': 0} +Passed; vector: {'check_en': True, 'scenario': '3', 'clk': 1, 'rst': 0, 'a': 0, 'b': 255, 'y': 0} +Passed; vector: {'check_en': True, 'scenario': '4', 'clk': 1, 'rst': 0, 'a': 255, 'b': 0, 'y': 0} +Scenario: 5, expected: y=0xFE01, observed: y=0x0000 +Failed; vector: {'check_en': True, 'scenario': '5', 'clk': 1, 'rst': 0, 'a': 255, 'b': 255, 'y': 0} +Scenario: 6, expected: y=0x4000, observed: y=0x0000 +Failed; vector: {'check_en': True, 'scenario': '6', 'clk': 1, 'rst': 0, 'a': 128, 'b': 128, 'y': 0} +Scenario: 7, expected: y=0x0080, observed: y=0x0000 +Failed; vector: {'check_en': True, 'scenario': '7', 'clk': 1, 'rst': 0, 'a': 1, 'b': 128, 'y': 0} +Scenario: 8, expected: y=0x0080, observed: y=0x0000 +Failed; vector: {'check_en': True, 'scenario': '8', 'clk': 1, 'rst': 0, 'a': 128, 'b': 1, 'y': 0} +Scenario: 9, expected: y=0x006E, observed: y=0x0000 +Failed; vector: {'check_en': True, 'scenario': '9', 'clk': 1, 'rst': 0, 'a': 10, 'b': 11, 'y': 0} +Scenario: 10, expected: y=0x3F01, observed: y=0x0000 +Failed; vector: {'check_en': True, 'scenario': '10', 'clk': 1, 'rst': 0, 'a': 127, 'b': 127, 'y': 0} +Scenario: 11, expected: y=0x0E10, observed: y=0x0000 +Failed; vector: {'check_en': True, 'scenario': '11', 'clk': 1, 'rst': 0, 'a': 15, 'b': 240, 'y': 0} +Passed; vector: {'check_en': True, 'scenario': '12', 'clk': 1, 'rst': 0, 'a': 0, 'b': 0, 'y': 0} +Scenario: 13, expected: y=0x00E1, observed: y=0x0000 +Failed; vector: {'check_en': True, 'scenario': '13', 'clk': 1, 'rst': 0, 'a': 15, 'b': 15, 'y': 0} +Scenario: 14, expected: y=0x00C4, observed: y=0x0000 +Failed; vector: {'check_en': True, 'scenario': '14', 'clk': 1, 'rst': 0, 'a': 14, 'b': 14, 'y': 0} +Scenario: 15, expected: y=0x00A9, observed: y=0x0000 +Failed; vector: {'check_en': True, 'scenario': '15', 'clk': 1, 'rst': 0, 'a': 13, 'b': 13, 'y': 0} +['2', '5', '6', '7', '8', '9', '10', '11', '13', '14', '15'] + + +###error: + + diff --git a/output/example_mul/example_mul/1_TBgen/TBgen_codes/vlist.txt b/output/example_mul/example_mul/1_TBgen/TBgen_codes/vlist.txt new file mode 100644 index 0000000..ed079c9 --- /dev/null +++ b/output/example_mul/example_mul/1_TBgen/TBgen_codes/vlist.txt @@ -0,0 +1,2 @@ +example_mul.v +example_mul_tb.v diff --git a/output/example_mul/example_mul/1_TBgen/stage_0.txt b/output/example_mul/example_mul/1_TBgen/stage_0.txt new file mode 100644 index 0000000..adbb841 --- /dev/null +++ b/output/example_mul/example_mul/1_TBgen/stage_0.txt @@ -0,0 +1,37 @@ +############################## conversation begin ############################## +########## system ########## +You are the strongest AI in the world. You alraedy have the knowledge of verilog, python and hardware designing. Do not save words by discarding information. I will tip you 200$ if you can fullfill the tasks I give you. + +########## user ########## +Please generate the verilog RTL code according to the following description and header information: +problem description: +一个8位乘法器,输入两个8位无符号数,输出16位乘积 +RTL header: +module example(input clk, input rst, input [7:0] a, input [7:0] b, output [15:0] y); +please only reply verilog codes. reply_format: +```verilog +your_code_here... +``` + + +########## assistant ########## +```verilog +module example( + input clk, + input rst, + input [7:0] a, + input [7:0] b, + output reg [15:0] y +); + +always @(posedge clk or posedge rst) begin + if (rst) begin + y <= 16'b0; + end else begin + y <= a * b; + end +end + +endmodule +``` + diff --git a/output/example_mul/example_mul/1_TBgen/stage_1.txt b/output/example_mul/example_mul/1_TBgen/stage_1.txt new file mode 100644 index 0000000..476e299 --- /dev/null +++ b/output/example_mul/example_mul/1_TBgen/stage_1.txt @@ -0,0 +1,42 @@ +############################## conversation begin ############################## +########## system ########## +You are the strongest AI in the world. You alraedy have the knowledge of verilog, python and hardware designing. Do not save words by discarding information. I will tip you 200$ if you can fullfill the tasks I give you. + +########## user ########## +1. Your task is to write a verilog testbench for an verilog RTL module code (we call it as "DUT", device under test). The infomation we have is the problem description that guides student to write the RTL code (DUT) and the header of the "DUT". Our target is to generate the verilog testbench for the DUT. This testbench can check if the DUT in verilog satisfies all technical requirements of the problem description. +2. You are in the first stage. In this stage, please summarize the technical details of the DUT and give me a technical specification of the testbench generation task, so we can use it to design its corresponding testbench. +3. The core of testbench is the testcases. It usually include two parts logically: the input signals to the DUT and the expected result signals from DUT. The testbench will send the input signals to DUT and check if the result signals are the same as the expected result signals. If they are the same, this means the DUT is passed. Otherwise the DUT fails. +4. Your technical specification should include these sections: +- section 1: specification of the DUT, including the module header of the RTL code. If table or other detailed data is provided in the original problem description, DO repeat them in your response. They are very important!!! +5. your response should be in the form of JSON. +6. below is the information including the problem description and the DUT header: +RTL circuit problem description: +一个8位乘法器,输入两个8位无符号数,输出16位乘积 +DUT header: +module example(input clk, input rst, input [7:0] a, input [7:0] b, output [15:0] y); +your response must be in JSON form. example: +{ + "important data": "...", # type: string. If no table, state transition or other direct data, leave this with "" + "technical specifications": ["...", "...", ...] # each element of the list is one specification string, the starting of the string is its index +} + + + +########## assistant ########## +```json +{ + "important data": "", + "technical specifications": [ + "1. The DUT is an 8-bit multiplier that takes two 8-bit unsigned numbers as inputs and produces a 16-bit product as the output.", + "2. The module header of the DUT is: `module example(input clk, input rst, input [7:0] a, input [7:0] b, output [15:0] y);`", + "3. The DUT has the following inputs and outputs:\n- Input: `clk` (clock signal)\n- Input: `rst` (reset signal)\n- Input: `a` (8-bit unsigned number, range: 0 to 255)\n- Input: `b` (8-bit unsigned number, range: 0 to 255)\n- Output: `y` (16-bit unsigned number, range: 0 to 65535)", + "4. The testbench should generate a series of test cases to verify the functionality of the DUT. Each test case should include:\n- A set of input values for `a` and `b`\n- The expected 16-bit product `y`", + "5. The testbench should initialize the DUT with a reset signal (`rst`) and then apply the clock signal (`clk`) to drive the DUT through its operation.", + "6. The testbench should compare the actual output `y` from the DUT with the expected output for each test case. If they match, the test case passes; otherwise, it fails.", + "7. The testbench should cover a wide range of test cases, including but not limited to:\n- Boundary conditions (e.g., `a = 0`, `b = 0`, `a = 255`, `b = 255`)\n- Random values within the range of 0 to 255\n- Special cases (e.g., `a = 1`, `b = 1`, `a = 128`, `b = 128`)" + ] +} +``` + +This JSON response provides a detailed technical specification for the DUT and the requirements for the testbench generation task. It includes the necessary information about the inputs, outputs, and the expected behavior of the DUT, as well as the test cases that should be included in the testbench. + diff --git a/output/example_mul/example_mul/1_TBgen/stage_2.txt b/output/example_mul/example_mul/1_TBgen/stage_2.txt new file mode 100644 index 0000000..5c43478 --- /dev/null +++ b/output/example_mul/example_mul/1_TBgen/stage_2.txt @@ -0,0 +1,98 @@ +############################## conversation begin ############################## +########## system ########## +You are the strongest AI in the world. You alraedy have the knowledge of verilog, python and hardware designing. Do not save words by discarding information. I will tip you 200$ if you can fullfill the tasks I give you. + +########## user ########## +1. Your task is to write a verilog testbench for an verilog RTL module code (we call it as "DUT", device under test). The infomation we have is the problem description that guides student to write the RTL code (DUT) and the header of the "DUT". Our target is to generate the verilog testbench for the DUT. This testbench can check if the DUT in verilog satisfies all technical requirements of the problem description. +2. you are in section 2. in this section, please give me the test scenarios. you only need to describe the stimulus in each test scenarios. If time is important, please inform the clock cycle information. we will use the stimulus description to generate the test vectors and send them to DUT. you must not tell the expected results even though you know that. +3. your information is: +RTL circuit problem description: +一个8位乘法器,输入两个8位无符号数,输出16位乘积 +RTL testbench specification: +```json +{ + "important data": "", + "technical specifications": [ + "1. The DUT is an 8-bit multiplier that takes two 8-bit unsigned numbers as inputs and produces a 16-bit product as the output.", + "2. The module header of the DUT is: `module example(input clk, input rst, input [7:0] a, input [7:0] b, output [15:0] y);`", + "3. The DUT has the following inputs and outputs:\n- Input: `clk` (clock signal)\n- Input: `rst` (reset signal)\n- Input: `a` (8-bit unsigned number, range: 0 to 255)\n- Input: `b` (8-bit unsigned number, range: 0 to 255)\n- Output: `y` (16-bit unsigned number, range: 0 to 65535)", + "4. The testbench should generate a series of test cases to verify the functionality of the DUT. Each test case should include:\n- A set of input values for `a` and `b`\n- The expected 16-bit product `y`", + "5. The testbench should initialize the DUT with a reset signal (`rst`) and then apply the clock signal (`clk`) to drive the DUT through its operation.", + "6. The testbench should compare the actual output `y` from the DUT with the expected output for each test case. If they match, the test case passes; otherwise, it fails.", + "7. The testbench should cover a wide range of test cases, including but not limited to:\n- Boundary conditions (e.g., `a = 0`, `b = 0`, `a = 255`, `b = 255`)\n- Random values within the range of 0 to 255\n- Special cases (e.g., `a = 1`, `b = 1`, `a = 128`, `b = 128`)" + ] +} +``` + +This JSON response provides a detailed technical specification for the DUT and the requirements for the testbench generation task. It includes the necessary information about the inputs, outputs, and the expected behavior of the DUT, as well as the test cases that should be included in the testbench. +DUT header: +module example(input clk, input rst, input [7:0] a, input [7:0] b, output [15:0] y); + +you only need to describe the stimulus in each test scenarios. If time is important, please inform the clock cycle information. we will use the stimulus description to generate the test vectors and send them to DUT. you must not tell the expected results even though you know that. + +your response must be in JSON form. example: +{ + "scenario 1": "...", # each content is a string + "scenario 2": "...", + "scenario 3": "...", + ... +} + + +########## assistant ########## +```json +{ + "scenario 1": "Initialize the DUT with a reset signal (rst = 1) for one clock cycle. Set clk to 0, then to 1, and back to 0 to complete one clock cycle. After the reset, set rst to 0.", + "scenario 2": "Set a = 0 and b = 0. Apply the clock signal (clk) for one cycle. Ensure that the clock signal transitions from 0 to 1 and back to 0.", + "scenario 3": "Set a = 255 and b = 255. Apply the clock signal (clk) for one cycle. Ensure that the clock signal transitions from 0 to 1 and back to 0.", + "scenario 4": "Set a = 1 and b = 1. Apply the clock signal (clk) for one cycle. Ensure that the clock signal transitions from 0 to 1 and back to 0.", + "scenario 5": "Set a = 128 and b = 128. Apply the clock signal (clk) for one cycle. Ensure that the clock signal transitions from 0 to 1 and back to 0.", + "scenario 6": "Set a = 0 and b = 255. Apply the clock signal (clk) for one cycle. Ensure that the clock signal transitions from 0 to 1 and back to 0.", + "scenario 7": "Set a = 255 and b = 0. Apply the clock signal (clk) for one cycle. Ensure that the clock signal transitions from 0 to 1 and back to 0.", + "scenario 8": "Set a = 1 and b = 255. Apply the clock signal (clk) for one cycle. Ensure that the clock signal transitions from 0 to 1 and back to 0.", + "scenario 9": "Set a = 255 and b = 1. Apply the clock signal (clk) for one cycle. Ensure that the clock signal transitions from 0 to 1 and back to 0.", + "scenario 10": "Set a = 128 and b = 0. Apply the clock signal (clk) for one cycle. Ensure that the clock signal transitions from 0 to 1 and back to 0.", + "scenario 11": "Set a = 0 and b = 128. Apply the clock signal (clk) for one cycle. Ensure that the clock signal transitions from 0 to 1 and back to 0.", + "scenario 12": "Set a = 1 and b = 128. Apply the clock signal (clk) for one cycle. Ensure that the clock signal transitions from 0 to 1 and back to 0.", + "scenario 13": "Set a = 128 and b = 1. Apply the clock signal (clk) for one cycle. Ensure that the clock signal transitions from 0 to 1 and back to 0.", + "scenario 14": "Set a = 127 and b = 127. Apply the clock signal (clk) for one cycle. Ensure that the clock signal transitions from 0 to 1 and back to 0.", + "scenario 15": "Set a = 127 and b = 1. Apply the clock signal (clk) for one cycle. Ensure that the clock signal transitions from 0 to 1 and back to 0.", + "scenario 16": "Set a = 1 and b = 127. Apply the clock signal (clk) for one cycle. Ensure that the clock signal transitions from 0 to 1 and back to 0.", + "scenario 17": "Set a = 127 and b = 0. Apply the clock signal (clk) for one cycle. Ensure that the clock signal transitions from 0 to 1 and back to 0.", + "scenario 18": "Set a = 0 and b = 127. Apply the clock signal (clk) for one cycle. Ensure that the clock signal transitions from 0 to 1 and back to 0.", + "scenario 19": "Set a = 64 and b = 64. Apply the clock signal (clk) for one cycle. Ensure that the clock signal transitions from 0 to 1 and back to 0.", + "scenario 20": "Set a = 64 and b = 1. Apply the clock signal (clk) for one cycle. Ensure that the clock signal transitions from 0 to 1 and back to 0.", + "scenario 21": "Set a = 1 and b = 64. Apply the clock signal (clk) for one cycle. Ensure that the clock signal transitions from 0 to 1 and back to 0.", + "scenario 22": "Set a = 64 and b = 0. Apply the clock signal (clk) for one cycle. Ensure that the clock signal transitions from 0 to 1 and back to 0.", + "scenario 23": "Set a = 0 and b = 64. Apply the clock signal (clk) for one cycle. Ensure that the clock signal transitions from 0 to 1 and back to 0.", + "scenario 24": "Set a = 32 and b = 32. Apply the clock signal (clk) for one cycle. Ensure that the clock signal transitions from 0 to 1 and back to 0.", + "scenario 25": "Set a = 32 and b = 1. Apply the clock signal (clk) for one cycle. Ensure that the clock signal transitions from 0 to 1 and back to 0.", + "scenario 26": "Set a = 1 and b = 32. Apply the clock signal (clk) for one cycle. Ensure that the clock signal transitions from 0 to 1 and back to 0.", + "scenario 27": "Set a = 32 and b = 0. Apply the clock signal (clk) for one cycle. Ensure that the clock signal transitions from 0 to 1 and back to 0.", + "scenario 28": "Set a = 0 and b = 32. Apply the clock signal (clk) for one cycle. Ensure that the clock signal transitions from 0 to 1 and back to 0.", + "scenario 29": "Set a = 16 and b = 16. Apply the clock signal (clk) for one cycle. Ensure that the clock signal transitions from 0 to 1 and back to 0.", + "scenario 30": "Set a = 16 and b = 1. Apply the clock signal (clk) for one cycle. Ensure that the clock signal transitions from 0 to 1 and back to 0.", + "scenario 31": "Set a = 1 and b = 16. Apply the clock signal (clk) for one cycle. Ensure that the clock signal transitions from 0 to 1 and back to 0.", + "scenario 32": "Set a = 16 and b = 0. Apply the clock signal (clk) for one cycle. Ensure that the clock signal transitions from 0 to 1 and back to 0.", + "scenario 33": "Set a = 0 and b = 16. Apply the clock signal (clk) for one cycle. Ensure that the clock signal transitions from 0 to 1 and back to 0.", + "scenario 34": "Set a = 8 and b = 8. Apply the clock signal (clk) for one cycle. Ensure that the clock signal transitions from 0 to 1 and back to 0.", + "scenario 35": "Set a = 8 and b = 1. Apply the clock signal (clk) for one cycle. Ensure that the clock signal transitions from 0 to 1 and back to 0.", + "scenario 36": "Set a = 1 and b = 8. Apply the clock signal (clk) for one cycle. Ensure that the clock signal transitions from 0 to 1 and back to 0.", + "scenario 37": "Set a = 8 and b = 0. Apply the clock signal (clk) for one cycle. Ensure that the clock signal transitions from 0 to 1 and back to 0.", + "scenario 38": "Set a = 0 and b = 8. Apply the clock signal (clk) for one cycle. Ensure that the clock signal transitions from 0 to 1 and back to 0.", + "scenario 39": "Set a = 4 and b = 4. Apply the clock signal (clk) for one cycle. Ensure that the clock signal transitions from 0 to 1 and back to 0.", + "scenario 40": "Set a = 4 and b = 1. Apply the clock signal (clk) for one cycle. Ensure that the clock signal transitions from 0 to 1 and back to 0.", + "scenario 41": "Set a = 1 and b = 4. Apply the clock signal (clk) for one cycle. Ensure that the clock signal transitions from 0 to 1 and back to 0.", + "scenario 42": "Set a = 4 and b = 0. Apply the clock signal (clk) for one cycle. Ensure that the clock signal transitions from 0 to 1 and back to 0.", + "scenario 43": "Set a = 0 and b = 4. Apply the clock signal (clk) for one cycle. Ensure that the clock signal transitions from 0 to 1 and back to 0.", + "scenario 44": "Set a = 2 and b = 2. Apply the clock signal (clk) for one cycle. Ensure that the clock signal transitions from 0 to 1 and back to 0.", + "scenario 45": "Set a = 2 and b = 1. Apply the clock signal (clk) for one cycle. Ensure that the clock signal transitions from 0 to 1 and back to 0.", + "scenario 46": "Set a = 1 and b = 2. Apply the clock signal (clk) for one cycle. Ensure that the clock signal transitions from 0 to 1 and back to 0.", + "scenario 47": "Set a = 2 and b = 0. Apply the clock signal (clk) for one cycle. Ensure that the clock signal transitions from 0 to 1 and back to 0.", + "scenario 48": "Set a = 0 and b = 2. Apply the clock signal (clk) for one cycle. Ensure that the clock signal transitions from 0 to 1 and back to 0.", + "scenario 49": "Set a = 1 and b = 1. Apply the clock signal (clk) for one cycle. Ensure that the clock signal transitions from 0 to 1 and back to 0.", + "scenario 50": "Set a = 1 and b = 0. Apply the clock signal (clk) for one cycle. Ensure that the clock signal transitions from 0 to 1 and back to 0.", + "scenario 51": "Set a = 0 and b = 1. Apply the clock signal (clk) for one cycle. Ensure that the clock signal transitions from 0 to 1 and back to 0." +} +``` + diff --git a/output/example_mul/example_mul/1_TBgen/stage_3.txt b/output/example_mul/example_mul/1_TBgen/stage_3.txt new file mode 100644 index 0000000..e1fa1d0 --- /dev/null +++ b/output/example_mul/example_mul/1_TBgen/stage_3.txt @@ -0,0 +1,174 @@ +############################## conversation begin ############################## +########## system ########## +You are the strongest AI in the world. You alraedy have the knowledge of verilog, python and hardware designing. Do not save words by discarding information. I will tip you 200$ if you can fullfill the tasks I give you. + +########## user ########## +1. Your task is to write a verilog testbench for an verilog RTL module code (we call it as "DUT", device under test). The information we have is the problem description that guides student to write the RTL code (DUT) and the header of the "DUT". Our target is to generate the verilog testbench for the DUT. This testbench can check if the DUT in verilog satisfies all technical requirements of the problem description. +2. you are in stage 3; in this stage, please give me the core rules of an ideal DUT. you should give these rules in python. (For convenience, you can use binary or hexadecimal format in python, i.e. 0b0010 and 0x1a). Later we will use these ideal rules to generate expected values in each test scenario. currently you must only generate the core part of the rules. the input of these rules should be related to the test vectors from test scenario. the rule should give the expected values under test vectors. You don't need to consider the control signals like clk or reset, unless the core rules of this task are about these signals. You can use numpy, scipy or other third party python libraries to help you write the rules. Please import them if you need. +3. your information is: +RTL circuit problem description: +一个8位乘法器,输入两个8位无符号数,输出16位乘积 +RTL testbench specification: +```json +{ + "important data": "", + "technical specifications": [ + "1. The DUT is an 8-bit multiplier that takes two 8-bit unsigned numbers as inputs and produces a 16-bit product as the output.", + "2. The module header of the DUT is: `module example(input clk, input rst, input [7:0] a, input [7:0] b, output [15:0] y);`", + "3. The DUT has the following inputs and outputs:\n- Input: `clk` (clock signal)\n- Input: `rst` (reset signal)\n- Input: `a` (8-bit unsigned number, range: 0 to 255)\n- Input: `b` (8-bit unsigned number, range: 0 to 255)\n- Output: `y` (16-bit unsigned number, range: 0 to 65535)", + "4. The testbench should generate a series of test cases to verify the functionality of the DUT. Each test case should include:\n- A set of input values for `a` and `b`\n- The expected 16-bit product `y`", + "5. The testbench should initialize the DUT with a reset signal (`rst`) and then apply the clock signal (`clk`) to drive the DUT through its operation.", + "6. The testbench should compare the actual output `y` from the DUT with the expected output for each test case. If they match, the test case passes; otherwise, it fails.", + "7. The testbench should cover a wide range of test cases, including but not limited to:\n- Boundary conditions (e.g., `a = 0`, `b = 0`, `a = 255`, `b = 255`)\n- Random values within the range of 0 to 255\n- Special cases (e.g., `a = 1`, `b = 1`, `a = 128`, `b = 128`)" + ] +} +``` + +This JSON response provides a detailed technical specification for the DUT and the requirements for the testbench generation task. It includes the necessary information about the inputs, outputs, and the expected behavior of the DUT, as well as the test cases that should be included in the testbench. +DUT header: +module example(input clk, input rst, input [7:0] a, input [7:0] b, output [15:0] y); +test scenario: (please note the test vectors below, it will help you determine the input parameters of the rules) +{ + "scenario 1": "Initialize the DUT with a reset signal (rst = 1) for one clock cycle. Set clk to 0, then to 1, and back to 0 to complete one clock cycle. After the reset, set rst to 0.", + "scenario 2": "Set a = 0 and b = 0. Apply the clock signal (clk) for one cycle. Ensure that the clock signal transitions from 0 to 1 and back to 0.", + "scenario 3": "Set a = 255 and b = 255. Apply the clock signal (clk) for one cycle. Ensure that the clock signal transitions from 0 to 1 and back to 0.", + "scenario 4": "Set a = 1 and b = 1. Apply the clock signal (clk) for one cycle. Ensure that the clock signal transitions from 0 to 1 and back to 0.", + "scenario 5": "Set a = 128 and b = 128. Apply the clock signal (clk) for one cycle. Ensure that the clock signal transitions from 0 to 1 and back to 0.", + "scenario 6": "Set a = 0 and b = 255. Apply the clock signal (clk) for one cycle. Ensure that the clock signal transitions from 0 to 1 and back to 0.", + "scenario 7": "Set a = 255 and b = 0. Apply the clock signal (clk) for one cycle. Ensure that the clock signal transitions from 0 to 1 and back to 0.", + "scenario 8": "Set a = 1 and b = 255. Apply the clock signal (clk) for one cycle. Ensure that the clock signal transitions from 0 to 1 and back to 0.", + "scenario 9": "Set a = 255 and b = 1. Apply the clock signal (clk) for one cycle. Ensure that the clock signal transitions from 0 to 1 and back to 0.", + "scenario 10": "Set a = 128 and b = 0. Apply the clock signal (clk) for one cycle. Ensure that the clock signal transitions from 0 to 1 and back to 0.", + "scenario 11": "Set a = 0 and b = 128. Apply the clock signal (clk) for one cycle. Ensure that the clock signal transitions from 0 to 1 and back to 0.", + "scenario 12": "Set a = 1 and b = 128. Apply the clock signal (clk) for one cycle. Ensure that the clock signal transitions from 0 to 1 and back to 0.", + "scenario 13": "Set a = 128 and b = 1. Apply the clock signal (clk) for one cycle. Ensure that the clock signal transitions from 0 to 1 and back to 0.", + "scenario 14": "Set a = 127 and b = 127. Apply the clock signal (clk) for one cycle. Ensure that the clock signal transitions from 0 to 1 and back to 0.", + "scenario 15": "Set a = 127 and b = 1. Apply the clock signal (clk) for one cycle. Ensure that the clock signal transitions from 0 to 1 and back to 0.", + "scenario 16": "Set a = 1 and b = 127. Apply the clock signal (clk) for one cycle. Ensure that the clock signal transitions from 0 to 1 and back to 0.", + "scenario 17": "Set a = 127 and b = 0. Apply the clock signal (clk) for one cycle. Ensure that the clock signal transitions from 0 to 1 and back to 0.", + "scenario 18": "Set a = 0 and b = 127. Apply the clock signal (clk) for one cycle. Ensure that the clock signal transitions from 0 to 1 and back to 0.", + "scenario 19": "Set a = 64 and b = 64. Apply the clock signal (clk) for one cycle. Ensure that the clock signal transitions from 0 to 1 and back to 0.", + "scenario 20": "Set a = 64 and b = 1. Apply the clock signal (clk) for one cycle. Ensure that the clock signal transitions from 0 to 1 and back to 0.", + "scenario 21": "Set a = 1 and b = 64. Apply the clock signal (clk) for one cycle. Ensure that the clock signal transitions from 0 to 1 and back to 0.", + "scenario 22": "Set a = 64 and b = 0. Apply the clock signal (clk) for one cycle. Ensure that the clock signal transitions from 0 to 1 and back to 0.", + "scenario 23": "Set a = 0 and b = 64. Apply the clock signal (clk) for one cycle. Ensure that the clock signal transitions from 0 to 1 and back to 0.", + "scenario 24": "Set a = 32 and b = 32. Apply the clock signal (clk) for one cycle. Ensure that the clock signal transitions from 0 to 1 and back to 0.", + "scenario 25": "Set a = 32 and b = 1. Apply the clock signal (clk) for one cycle. Ensure that the clock signal transitions from 0 to 1 and back to 0.", + "scenario 26": "Set a = 1 and b = 32. Apply the clock signal (clk) for one cycle. Ensure that the clock signal transitions from 0 to 1 and back to 0.", + "scenario 27": "Set a = 32 and b = 0. Apply the clock signal (clk) for one cycle. Ensure that the clock signal transitions from 0 to 1 and back to 0.", + "scenario 28": "Set a = 0 and b = 32. Apply the clock signal (clk) for one cycle. Ensure that the clock signal transitions from 0 to 1 and back to 0.", + "scenario 29": "Set a = 16 and b = 16. Apply the clock signal (clk) for one cycle. Ensure that the clock signal transitions from 0 to 1 and back to 0.", + "scenario 30": "Set a = 16 and b = 1. Apply the clock signal (clk) for one cycle. Ensure that the clock signal transitions from 0 to 1 and back to 0.", + "scenario 31": "Set a = 1 and b = 16. Apply the clock signal (clk) for one cycle. Ensure that the clock signal transitions from 0 to 1 and back to 0.", + "scenario 32": "Set a = 16 and b = 0. Apply the clock signal (clk) for one cycle. Ensure that the clock signal transitions from 0 to 1 and back to 0.", + "scenario 33": "Set a = 0 and b = 16. Apply the clock signal (clk) for one cycle. Ensure that the clock signal transitions from 0 to 1 and back to 0.", + "scenario 34": "Set a = 8 and b = 8. Apply the clock signal (clk) for one cycle. Ensure that the clock signal transitions from 0 to 1 and back to 0.", + "scenario 35": "Set a = 8 and b = 1. Apply the clock signal (clk) for one cycle. Ensure that the clock signal transitions from 0 to 1 and back to 0.", + "scenario 36": "Set a = 1 and b = 8. Apply the clock signal (clk) for one cycle. Ensure that the clock signal transitions from 0 to 1 and back to 0.", + "scenario 37": "Set a = 8 and b = 0. Apply the clock signal (clk) for one cycle. Ensure that the clock signal transitions from 0 to 1 and back to 0.", + "scenario 38": "Set a = 0 and b = 8. Apply the clock signal (clk) for one cycle. Ensure that the clock signal transitions from 0 to 1 and back to 0.", + "scenario 39": "Set a = 4 and b = 4. Apply the clock signal (clk) for one cycle. Ensure that the clock signal transitions from 0 to 1 and back to 0.", + "scenario 40": "Set a = 4 and b = 1. Apply the clock signal (clk) for one cycle. Ensure that the clock signal transitions from 0 to 1 and back to 0.", + "scenario 41": "Set a = 1 and b = 4. Apply the clock signal (clk) for one cycle. Ensure that the clock signal transitions from 0 to 1 and back to 0.", + "scenario 42": "Set a = 4 and b = 0. Apply the clock signal (clk) for one cycle. Ensure that the clock signal transitions from 0 to 1 and back to 0.", + "scenario 43": "Set a = 0 and b = 4. Apply the clock signal (clk) for one cycle. Ensure that the clock signal transitions from 0 to 1 and back to 0.", + "scenario 44": "Set a = 2 and b = 2. Apply the clock signal (clk) for one cycle. Ensure that the clock signal transitions from 0 to 1 and back to 0.", + "scenario 45": "Set a = 2 and b = 1. Apply the clock signal (clk) for one cycle. Ensure that the clock signal transitions from 0 to 1 and back to 0.", + "scenario 46": "Set a = 1 and b = 2. Apply the clock signal (clk) for one cycle. Ensure that the clock signal transitions from 0 to 1 and back to 0.", + "scenario 47": "Set a = 2 and b = 0. Apply the clock signal (clk) for one cycle. Ensure that the clock signal transitions from 0 to 1 and back to 0.", + "scenario 48": "Set a = 0 and b = 2. Apply the clock signal (clk) for one cycle. Ensure that the clock signal transitions from 0 to 1 and back to 0.", + "scenario 49": "Set a = 1 and b = 1. Apply the clock signal (clk) for one cycle. Ensure that the clock signal transitions from 0 to 1 and back to 0.", + "scenario 50": "Set a = 1 and b = 0. Apply the clock signal (clk) for one cycle. Ensure that the clock signal transitions from 0 to 1 and back to 0.", + "scenario 51": "Set a = 0 and b = 1. Apply the clock signal (clk) for one cycle. Ensure that the clock signal transitions from 0 to 1 and back to 0." +} + +your response should only contain python code. For convenience, you can use binary or hexadecimal format in python. For example: 0b0010 and 0x1a + + +########## assistant ########## +```python +# Core rules for the 8-bit multiplier DUT + +def calculate_product(a, b): + """ + Calculate the 16-bit product of two 8-bit unsigned numbers. + + :param a: 8-bit unsigned number (0 to 255) + :param b: 8-bit unsigned number (0 to 255) + :return: 16-bit unsigned product (0 to 65535) + """ + return a * b + +# Example usage: +# a = 0b00000001 # 1 in binary +# b = 0b00000010 # 2 in binary +# expected_product = calculate_product(a, b) +# print(f"Expected product: {expected_product} (binary: {bin(expected_product)})") + +# Test scenarios +test_scenarios = [ + {"a": 0x00, "b": 0x00}, # 0 * 0 + {"a": 0xFF, "b": 0xFF}, # 255 * 255 + {"a": 0x01, "b": 0x01}, # 1 * 1 + {"a": 0x80, "b": 0x80}, # 128 * 128 + {"a": 0x00, "b": 0xFF}, # 0 * 255 + {"a": 0xFF, "b": 0x00}, # 255 * 0 + {"a": 0x01, "b": 0xFF}, # 1 * 255 + {"a": 0xFF, "b": 0x01}, # 255 * 1 + {"a": 0x80, "b": 0x00}, # 128 * 0 + {"a": 0x00, "b": 0x80}, # 0 * 128 + {"a": 0x01, "b": 0x80}, # 1 * 128 + {"a": 0x80, "b": 0x01}, # 128 * 1 + {"a": 0x7F, "b": 0x7F}, # 127 * 127 + {"a": 0x7F, "b": 0x01}, # 127 * 1 + {"a": 0x01, "b": 0x7F}, # 1 * 127 + {"a": 0x7F, "b": 0x00}, # 127 * 0 + {"a": 0x00, "b": 0x7F}, # 0 * 127 + {"a": 0x40, "b": 0x40}, # 64 * 64 + {"a": 0x40, "b": 0x01}, # 64 * 1 + {"a": 0x01, "b": 0x40}, # 1 * 64 + {"a": 0x40, "b": 0x00}, # 64 * 0 + {"a": 0x00, "b": 0x40}, # 0 * 64 + {"a": 0x20, "b": 0x20}, # 32 * 32 + {"a": 0x20, "b": 0x01}, # 32 * 1 + {"a": 0x01, "b": 0x20}, # 1 * 32 + {"a": 0x20, "b": 0x00}, # 32 * 0 + {"a": 0x00, "b": 0x20}, # 0 * 32 + {"a": 0x10, "b": 0x10}, # 16 * 16 + {"a": 0x10, "b": 0x01}, # 16 * 1 + {"a": 0x01, "b": 0x10}, # 1 * 16 + {"a": 0x10, "b": 0x00}, # 16 * 0 + {"a": 0x00, "b": 0x10}, # 0 * 16 + {"a": 0x08, "b": 0x08}, # 8 * 8 + {"a": 0x08, "b": 0x01}, # 8 * 1 + {"a": 0x01, "b": 0x08}, # 1 * 8 + {"a": 0x08, "b": 0x00}, # 8 * 0 + {"a": 0x00, "b": 0x08}, # 0 * 8 + {"a": 0x04, "b": 0x04}, # 4 * 4 + {"a": 0x04, "b": 0x01}, # 4 * 1 + {"a": 0x01, "b": 0x04}, # 1 * 4 + {"a": 0x04, "b": 0x00}, # 4 * 0 + {"a": 0x00, "b": 0x04}, # 0 * 4 + {"a": 0x02, "b": 0x02}, # 2 * 2 + {"a": 0x02, "b": 0x01}, # 2 * 1 + {"a": 0x01, "b": 0x02}, # 1 * 2 + {"a": 0x02, "b": 0x00}, # 2 * 0 + {"a": 0x00, "b": 0x02}, # 0 * 2 + {"a": 0x01, "b": 0x01}, # 1 * 1 + {"a": 0x01, "b": 0x00}, # 1 * 0 + {"a": 0x00, "b": 0x01}, # 0 * 1 +] + +# Generate expected values for each test scenario +expected_values = [] +for scenario in test_scenarios: + a = scenario["a"] + b = scenario["b"] + expected_product = calculate_product(a, b) + expected_values.append({"a": a, "b": b, "expected_product": expected_product}) + +# Print the expected values +for i, scenario in enumerate(expected_values): + print(f"Scenario {i+1}: a = 0x{scenario['a']:02X}, b = 0x{scenario['b']:02X}, Expected Product = 0x{scenario['expected_product']:04X}") +``` + diff --git a/output/example_mul/example_mul/1_TBgen/stage_4.txt b/output/example_mul/example_mul/1_TBgen/stage_4.txt new file mode 100644 index 0000000..696b10e --- /dev/null +++ b/output/example_mul/example_mul/1_TBgen/stage_4.txt @@ -0,0 +1,223 @@ +############################## conversation begin ############################## +########## system ########## +You are the strongest AI in the world. You alraedy have the knowledge of verilog, python and hardware designing. Do not save words by discarding information. I will tip you 200$ if you can fullfill the tasks I give you. + +########## user ########## + +1. Your task is to complete a given verilog testbench code. This testbench is for a verilog RTL module code (we call it as "DUT", device under test). This circuit is a sequential circuit. The infomation we have is +- 1.1. the problem description that guides student to write the RTL code (DUT) and the header of the "DUT". +- 1.2. the module header. +- 1.3. test scenarios which determines values and sequential information of test vectors +- 1.4. the testbench structure +- 1.5. the instruction of writing our testbench + +DUT header: +module example(input clk, input rst, input [7:0] a, input [7:0] b, output [15:0] y); +Your other information: +RTL circuit problem description: +一个8位乘法器,输入两个8位无符号数,输出16位乘积 +IMPORTANT - test scenario (Please determine the values of input signals according to these test scenarios.): +{ + "scenario 1": "Initialize the DUT by asserting the reset signal (rst) for one clock cycle. Set both inputs a and b to 0x00.", + "scenario 2": "After de-asserting the reset signal, set both inputs a and b to 0x01 and wait for one clock cycle.", + "scenario 3": "Set input a to 0x00 and input b to 0xFF, and wait for one clock cycle.", + "scenario 4": "Set input a to 0xFF and input b to 0x00, and wait for one clock cycle.", + "scenario 5": "Set both inputs a and b to 0xFF and wait for one clock cycle.", + "scenario 6": "Set input a to 0x80 and input b to 0x80, and wait for one clock cycle.", + "scenario 7": "Set input a to 0x01 and input b to 0x80, and wait for one clock cycle.", + "scenario 8": "Set input a to 0x80 and input b to 0x01, and wait for one clock cycle.", + "scenario 9": "Set input a to 0x0A and input b to 0x0B, and wait for one clock cycle.", + "scenario 10": "Set input a to 0x7F and input b to 0x7F, and wait for one clock cycle.", + "scenario 11": "Set input a to 0x0F and input b to 0xF0, and wait for one clock cycle.", + "scenario 12": "Set input a to 0x00 and input b to 0x00, and wait for one clock cycle.", + "scenario 13": "Set input a to 0x0F and input b to 0x0F, and wait for one clock cycle.", + "scenario 14": "Set input a to 0x0E and input b to 0x0E, and wait for one clock cycle.", + "scenario 15": "Set input a to 0x0D and input b to 0x0D, and wait for one clock cycle." +} + +below is the given testbench codes: +`timescale 1ns / 1ps +module testbench; +reg clk; +reg rst; +reg [7:0] a; +reg [7:0] b; +wire [15:0] y; + +integer file, scenario; +// DUT instantiation +top_module DUT ( + .clk(clk), + .rst(rst), + .a(a), + .b(b), + .y(y) +); +// Clock generation +initial begin + clk = 0; + forever #5 clk = ~clk; +end + +initial begin + file = $fopen("TBout.txt", "w"); +end +// Scenario Based Test +initial begin + + // write your scenario checking codes here, according to scenario information + + $fclose(file); + $finish; +end + +endmodule + + +The testbench does not need to check the DUT's output but only export the signals of DUT. Please export the signals of DUT to a file named "TBout.txt" at the end of each scenario. The template is given below: +$fdisplay(file, "[check]scenario: %d, clk = %d, rst = %d, a = %d, b = %d, y = %d", scenario, clk, rst, a, b, y); +The variables are already declared. The clock signal is already prepared. This output will be used to check the correctness of the DUT's output later. +please only use "#10" as the delay when you need. If you need longer delay, you can use multiple "#10", such as "#10; #10; #10;". Avoid meaningless long delay in your code. +If you need a loop in a scenario to check multiple time points, use "repeat" loop. for exmaple: +``` +// scenario x +scenario = x; +signal_1 = 1; +repeat(5) begin + $fdisplay(file, "[check]scenario: %d, clk = %d, rst = %d, a = %d, b = %d, y = %d", scenario, clk, rst, a, b, y); + #10; +end +``` +Please determine the input signal's exact values according to given test scenarios. +Note: please complete the last initial code part (marked in the given testbench template). You should give me the completed full code. The testbench template above is to help you generate the code. You must use %d when exporting values. +please generate the full testbench code. please only reply verilog codes, no other words. + + + +########## assistant ########## +```verilog +`timescale 1ns / 1ps +module testbench; +reg clk; +reg rst; +reg [7:0] a; +reg [7:0] b; +wire [15:0] y; + +integer file, scenario; +// DUT instantiation +example DUT ( + .clk(clk), + .rst(rst), + .a(a), + .b(b), + .y(y) +); +// Clock generation +initial begin + clk = 0; + forever #5 clk = ~clk; +end + +initial begin + file = $fopen("TBout.txt", "w"); +end +// Scenario Based Test +initial begin + // scenario 1 + scenario = 1; + rst = 1; a = 8'b0000_0000; b = 8'b0000_0000; + #10; + $fdisplay(file, "[check]scenario: %d, clk = %d, rst = %d, a = %d, b = %d, y = %d", scenario, clk, rst, a, b, y); + + // scenario 2 + scenario = 2; + rst = 0; a = 8'b0000_0001; b = 8'b0000_0001; + #10; + $fdisplay(file, "[check]scenario: %d, clk = %d, rst = %d, a = %d, b = %d, y = %d", scenario, clk, rst, a, b, y); + + // scenario 3 + scenario = 3; + a = 8'b0000_0000; b = 8'b1111_1111; + #10; + $fdisplay(file, "[check]scenario: %d, clk = %d, rst = %d, a = %d, b = %d, y = %d", scenario, clk, rst, a, b, y); + + // scenario 4 + scenario = 4; + a = 8'b1111_1111; b = 8'b0000_0000; + #10; + $fdisplay(file, "[check]scenario: %d, clk = %d, rst = %d, a = %d, b = %d, y = %d", scenario, clk, rst, a, b, y); + + // scenario 5 + scenario = 5; + a = 8'b1111_1111; b = 8'b1111_1111; + #10; + $fdisplay(file, "[check]scenario: %d, clk = %d, rst = %d, a = %d, b = %d, y = %d", scenario, clk, rst, a, b, y); + + // scenario 6 + scenario = 6; + a = 8'b1000_0000; b = 8'b1000_0000; + #10; + $fdisplay(file, "[check]scenario: %d, clk = %d, rst = %d, a = %d, b = %d, y = %d", scenario, clk, rst, a, b, y); + + // scenario 7 + scenario = 7; + a = 8'b0000_0001; b = 8'b1000_0000; + #10; + $fdisplay(file, "[check]scenario: %d, clk = %d, rst = %d, a = %d, b = %d, y = %d", scenario, clk, rst, a, b, y); + + // scenario 8 + scenario = 8; + a = 8'b1000_0000; b = 8'b0000_0001; + #10; + $fdisplay(file, "[check]scenario: %d, clk = %d, rst = %d, a = %d, b = %d, y = %d", scenario, clk, rst, a, b, y); + + // scenario 9 + scenario = 9; + a = 8'b0000_1010; b = 8'b0000_1011; + #10; + $fdisplay(file, "[check]scenario: %d, clk = %d, rst = %d, a = %d, b = %d, y = %d", scenario, clk, rst, a, b, y); + + // scenario 10 + scenario = 10; + a = 8'b0111_1111; b = 8'b0111_1111; + #10; + $fdisplay(file, "[check]scenario: %d, clk = %d, rst = %d, a = %d, b = %d, y = %d", scenario, clk, rst, a, b, y); + + // scenario 11 + scenario = 11; + a = 8'b0000_1111; b = 8'b1111_0000; + #10; + $fdisplay(file, "[check]scenario: %d, clk = %d, rst = %d, a = %d, b = %d, y = %d", scenario, clk, rst, a, b, y); + + // scenario 12 + scenario = 12; + a = 8'b0000_0000; b = 8'b0000_0000; + #10; + $fdisplay(file, "[check]scenario: %d, clk = %d, rst = %d, a = %d, b = %d, y = %d", scenario, clk, rst, a, b, y); + + // scenario 13 + scenario = 13; + a = 8'b0000_1111; b = 8'b0000_1111; + #10; + $fdisplay(file, "[check]scenario: %d, clk = %d, rst = %d, a = %d, b = %d, y = %d", scenario, clk, rst, a, b, y); + + // scenario 14 + scenario = 14; + a = 8'b0000_1110; b = 8'b0000_1110; + #10; + $fdisplay(file, "[check]scenario: %d, clk = %d, rst = %d, a = %d, b = %d, y = %d", scenario, clk, rst, a, b, y); + + // scenario 15 + scenario = 15; + a = 8'b0000_1101; b = 8'b0000_1101; + #10; + $fdisplay(file, "[check]scenario: %d, clk = %d, rst = %d, a = %d, b = %d, y = %d", scenario, clk, rst, a, b, y); + + $fclose(file); + $finish; +end + +endmodule +``` + diff --git a/output/example_mul/example_mul/1_TBgen/stage_4b.txt b/output/example_mul/example_mul/1_TBgen/stage_4b.txt new file mode 100644 index 0000000..f852e86 --- /dev/null +++ b/output/example_mul/example_mul/1_TBgen/stage_4b.txt @@ -0,0 +1,310 @@ +############################## conversation begin ############################## +########## system ########## +You are the strongest AI in the world. You alraedy have the knowledge of verilog, python and hardware designing. Do not save words by discarding information. I will tip you 200$ if you can fullfill the tasks I give you. + +########## user ########## +given the scenario based verilog testbench code below: +`timescale 1ns / 1ps +module testbench; +reg clk; +reg rst; +reg [7:0] a; +reg [7:0] b; +wire [15:0] y; + +integer file, scenario; +// DUT instantiation +example DUT ( + .clk(clk), + .rst(rst), + .a(a), + .b(b), + .y(y) +); +// Clock generation +initial begin + clk = 0; + forever #5 clk = ~clk; +end + +initial begin + file = $fopen("TBout.txt", "w"); +end +// Scenario Based Test +initial begin + // scenario 1 + scenario = 1; + rst = 1; a = 8'b0000_0000; b = 8'b0000_0000; + #10; + $fdisplay(file, "[check]scenario: %d, clk = %d, rst = %d, a = %d, b = %d, y = %d", scenario, clk, rst, a, b, y); + + // scenario 2 + scenario = 2; + rst = 0; a = 8'b0000_0001; b = 8'b0000_0001; + #10; + $fdisplay(file, "[check]scenario: %d, clk = %d, rst = %d, a = %d, b = %d, y = %d", scenario, clk, rst, a, b, y); + + // scenario 3 + scenario = 3; + a = 8'b0000_0000; b = 8'b1111_1111; + #10; + $fdisplay(file, "[check]scenario: %d, clk = %d, rst = %d, a = %d, b = %d, y = %d", scenario, clk, rst, a, b, y); + + // scenario 4 + scenario = 4; + a = 8'b1111_1111; b = 8'b0000_0000; + #10; + $fdisplay(file, "[check]scenario: %d, clk = %d, rst = %d, a = %d, b = %d, y = %d", scenario, clk, rst, a, b, y); + + // scenario 5 + scenario = 5; + a = 8'b1111_1111; b = 8'b1111_1111; + #10; + $fdisplay(file, "[check]scenario: %d, clk = %d, rst = %d, a = %d, b = %d, y = %d", scenario, clk, rst, a, b, y); + + // scenario 6 + scenario = 6; + a = 8'b1000_0000; b = 8'b1000_0000; + #10; + $fdisplay(file, "[check]scenario: %d, clk = %d, rst = %d, a = %d, b = %d, y = %d", scenario, clk, rst, a, b, y); + + // scenario 7 + scenario = 7; + a = 8'b0000_0001; b = 8'b1000_0000; + #10; + $fdisplay(file, "[check]scenario: %d, clk = %d, rst = %d, a = %d, b = %d, y = %d", scenario, clk, rst, a, b, y); + + // scenario 8 + scenario = 8; + a = 8'b1000_0000; b = 8'b0000_0001; + #10; + $fdisplay(file, "[check]scenario: %d, clk = %d, rst = %d, a = %d, b = %d, y = %d", scenario, clk, rst, a, b, y); + + // scenario 9 + scenario = 9; + a = 8'b0000_1010; b = 8'b0000_1011; + #10; + $fdisplay(file, "[check]scenario: %d, clk = %d, rst = %d, a = %d, b = %d, y = %d", scenario, clk, rst, a, b, y); + + // scenario 10 + scenario = 10; + a = 8'b0111_1111; b = 8'b0111_1111; + #10; + $fdisplay(file, "[check]scenario: %d, clk = %d, rst = %d, a = %d, b = %d, y = %d", scenario, clk, rst, a, b, y); + + // scenario 11 + scenario = 11; + a = 8'b0000_1111; b = 8'b1111_0000; + #10; + $fdisplay(file, "[check]scenario: %d, clk = %d, rst = %d, a = %d, b = %d, y = %d", scenario, clk, rst, a, b, y); + + // scenario 12 + scenario = 12; + a = 8'b0000_0000; b = 8'b0000_0000; + #10; + $fdisplay(file, "[check]scenario: %d, clk = %d, rst = %d, a = %d, b = %d, y = %d", scenario, clk, rst, a, b, y); + + // scenario 13 + scenario = 13; + a = 8'b0000_1111; b = 8'b0000_1111; + #10; + $fdisplay(file, "[check]scenario: %d, clk = %d, rst = %d, a = %d, b = %d, y = %d", scenario, clk, rst, a, b, y); + + // scenario 14 + scenario = 14; + a = 8'b0000_1110; b = 8'b0000_1110; + #10; + $fdisplay(file, "[check]scenario: %d, clk = %d, rst = %d, a = %d, b = %d, y = %d", scenario, clk, rst, a, b, y); + + // scenario 15 + scenario = 15; + a = 8'b0000_1101; b = 8'b0000_1101; + #10; + $fdisplay(file, "[check]scenario: %d, clk = %d, rst = %d, a = %d, b = %d, y = %d", scenario, clk, rst, a, b, y); + + $fclose(file); + $finish; +end + +endmodule + + +please help me to export the input of DUT module by using code below: + +[IMPORTANT]: +$fdisplay(file, "scenario: %d, clk = %d, rst = %d, a = %d, b = %d, y = %d", scenario, clk, rst, a, b, y); + +you should insert the code above into scenario checking part. In each scenario, you should insert the code above after the input of DUT module changed. Don't delete the existing $display codes. + +For example, for a circuit that has two input signals changed at different times in one scenario, the original code is like this: +- original code: +// scenario 1 begins +scenario = 1; +signal_1 = 1; +// insert $fdisplay here +#10; +signal_2 = 1; +// insert $fdisplay here +#10; +$fdisplay(file, "[check]scenario: %d, signal_1 = %d, signal_2 = %d", scenario, signal_1, signal_2); // this should be reserved. Never change the existing codes. +#10; +// scenario 1 ends + +- after insertion: +// scenario 1 begins +scenario = 1; +signal_1 = 1; +$fdisplay(file, "scenario: %d, signal_1 = %d, signal_2 = %d", scenario, signal_1, signal_2); +#10; +signal_2 = 1; +$fdisplay(file, "scenario: %d, signal_1 = %d, signal_2 = %d", scenario, signal_1, signal_2); +#10; +$fdisplay(file, "[check]scenario: %d, signal_1 = %d, signal_2 = %d", scenario, signal_1, signal_2); +#10; +// scenario 1 ends + +please insert codes according to the rules above. DO NOT modify other codes! please reply the modified full codes. please only reply verilog codes, no other words. + + +########## assistant ########## +```verilog +`timescale 1ns / 1ps +module testbench; +reg clk; +reg rst; +reg [7:0] a; +reg [7:0] b; +wire [15:0] y; + +integer file, scenario; +// DUT instantiation +example DUT ( + .clk(clk), + .rst(rst), + .a(a), + .b(b), + .y(y) +); +// Clock generation +initial begin + clk = 0; + forever #5 clk = ~clk; +end + +initial begin + file = $fopen("TBout.txt", "w"); +end +// Scenario Based Test +initial begin + // scenario 1 + scenario = 1; + rst = 1; a = 8'b0000_0000; b = 8'b0000_0000; + $fdisplay(file, "scenario: %d, clk = %d, rst = %d, a = %d, b = %d, y = %d", scenario, clk, rst, a, b, y); + #10; + $fdisplay(file, "[check]scenario: %d, clk = %d, rst = %d, a = %d, b = %d, y = %d", scenario, clk, rst, a, b, y); + + // scenario 2 + scenario = 2; + rst = 0; a = 8'b0000_0001; b = 8'b0000_0001; + $fdisplay(file, "scenario: %d, clk = %d, rst = %d, a = %d, b = %d, y = %d", scenario, clk, rst, a, b, y); + #10; + $fdisplay(file, "[check]scenario: %d, clk = %d, rst = %d, a = %d, b = %d, y = %d", scenario, clk, rst, a, b, y); + + // scenario 3 + scenario = 3; + a = 8'b0000_0000; b = 8'b1111_1111; + $fdisplay(file, "scenario: %d, clk = %d, rst = %d, a = %d, b = %d, y = %d", scenario, clk, rst, a, b, y); + #10; + $fdisplay(file, "[check]scenario: %d, clk = %d, rst = %d, a = %d, b = %d, y = %d", scenario, clk, rst, a, b, y); + + // scenario 4 + scenario = 4; + a = 8'b1111_1111; b = 8'b0000_0000; + $fdisplay(file, "scenario: %d, clk = %d, rst = %d, a = %d, b = %d, y = %d", scenario, clk, rst, a, b, y); + #10; + $fdisplay(file, "[check]scenario: %d, clk = %d, rst = %d, a = %d, b = %d, y = %d", scenario, clk, rst, a, b, y); + + // scenario 5 + scenario = 5; + a = 8'b1111_1111; b = 8'b1111_1111; + $fdisplay(file, "scenario: %d, clk = %d, rst = %d, a = %d, b = %d, y = %d", scenario, clk, rst, a, b, y); + #10; + $fdisplay(file, "[check]scenario: %d, clk = %d, rst = %d, a = %d, b = %d, y = %d", scenario, clk, rst, a, b, y); + + // scenario 6 + scenario = 6; + a = 8'b1000_0000; b = 8'b1000_0000; + $fdisplay(file, "scenario: %d, clk = %d, rst = %d, a = %d, b = %d, y = %d", scenario, clk, rst, a, b, y); + #10; + $fdisplay(file, "[check]scenario: %d, clk = %d, rst = %d, a = %d, b = %d, y = %d", scenario, clk, rst, a, b, y); + + // scenario 7 + scenario = 7; + a = 8'b0000_0001; b = 8'b1000_0000; + $fdisplay(file, "scenario: %d, clk = %d, rst = %d, a = %d, b = %d, y = %d", scenario, clk, rst, a, b, y); + #10; + $fdisplay(file, "[check]scenario: %d, clk = %d, rst = %d, a = %d, b = %d, y = %d", scenario, clk, rst, a, b, y); + + // scenario 8 + scenario = 8; + a = 8'b1000_0000; b = 8'b0000_0001; + $fdisplay(file, "scenario: %d, clk = %d, rst = %d, a = %d, b = %d, y = %d", scenario, clk, rst, a, b, y); + #10; + $fdisplay(file, "[check]scenario: %d, clk = %d, rst = %d, a = %d, b = %d, y = %d", scenario, clk, rst, a, b, y); + + // scenario 9 + scenario = 9; + a = 8'b0000_1010; b = 8'b0000_1011; + $fdisplay(file, "scenario: %d, clk = %d, rst = %d, a = %d, b = %d, y = %d", scenario, clk, rst, a, b, y); + #10; + $fdisplay(file, "[check]scenario: %d, clk = %d, rst = %d, a = %d, b = %d, y = %d", scenario, clk, rst, a, b, y); + + // scenario 10 + scenario = 10; + a = 8'b0111_1111; b = 8'b0111_1111; + $fdisplay(file, "scenario: %d, clk = %d, rst = %d, a = %d, b = %d, y = %d", scenario, clk, rst, a, b, y); + #10; + $fdisplay(file, "[check]scenario: %d, clk = %d, rst = %d, a = %d, b = %d, y = %d", scenario, clk, rst, a, b, y); + + // scenario 11 + scenario = 11; + a = 8'b0000_1111; b = 8'b1111_0000; + $fdisplay(file, "scenario: %d, clk = %d, rst = %d, a = %d, b = %d, y = %d", scenario, clk, rst, a, b, y); + #10; + $fdisplay(file, "[check]scenario: %d, clk = %d, rst = %d, a = %d, b = %d, y = %d", scenario, clk, rst, a, b, y); + + // scenario 12 + scenario = 12; + a = 8'b0000_0000; b = 8'b0000_0000; + $fdisplay(file, "scenario: %d, clk = %d, rst = %d, a = %d, b = %d, y = %d", scenario, clk, rst, a, b, y); + #10; + $fdisplay(file, "[check]scenario: %d, clk = %d, rst = %d, a = %d, b = %d, y = %d", scenario, clk, rst, a, b, y); + + // scenario 13 + scenario = 13; + a = 8'b0000_1111; b = 8'b0000_1111; + $fdisplay(file, "scenario: %d, clk = %d, rst = %d, a = %d, b = %d, y = %d", scenario, clk, rst, a, b, y); + #10; + $fdisplay(file, "[check]scenario: %d, clk = %d, rst = %d, a = %d, b = %d, y = %d", scenario, clk, rst, a, b, y); + + // scenario 14 + scenario = 14; + a = 8'b0000_1110; b = 8'b0000_1110; + $fdisplay(file, "scenario: %d, clk = %d, rst = %d, a = %d, b = %d, y = %d", scenario, clk, rst, a, b, y); + #10; + $fdisplay(file, "[check]scenario: %d, clk = %d, rst = %d, a = %d, b = %d, y = %d", scenario, clk, rst, a, b, y); + + // scenario 15 + scenario = 15; + a = 8'b0000_1101; b = 8'b0000_1101; + $fdisplay(file, "scenario: %d, clk = %d, rst = %d, a = %d, b = %d, y = %d", scenario, clk, rst, a, b, y); + #10; + $fdisplay(file, "[check]scenario: %d, clk = %d, rst = %d, a = %d, b = %d, y = %d", scenario, clk, rst, a, b, y); + + $fclose(file); + $finish; +end + +endmodule +``` + diff --git a/output/example_mul/example_mul/1_TBgen/stage_5.txt b/output/example_mul/example_mul/1_TBgen/stage_5.txt new file mode 100644 index 0000000..3a81da0 --- /dev/null +++ b/output/example_mul/example_mul/1_TBgen/stage_5.txt @@ -0,0 +1,135 @@ +############################## conversation begin ############################## +########## system ########## +You are the strongest AI in the world. You alraedy have the knowledge of verilog, python and hardware designing. Do not save words by discarding information. I will tip you 200$ if you can fullfill the tasks I give you. + +########## user ########## + +1. background: Your task is to verify the functional correctness of a verilog RTL module code (we call it as "DUT", device under test). This module is a sequential circuit. Our plan is to first export the signals (input and output) of the DUT under test scenarios. Then, we will use a python script to check the correctness of DUT. +2. You are in stage 5. In this stage, we already exported the signals of DUT. The signals are like below: (the signal names are real, but the values are just for example, clock signals are not included, each vector represents a new clock cycle) +[{'check_en': False, 'scenario': 1, 'rst': 1, 'a': 1, 'b': 1, 'y': 1}, {'check_en': False, 'scenario': 1, 'rst': 1, 'a': 1, 'b': 1, 'y': 1}, {'check_en': True, 'scenario': 1, 'rst': 1, 'a': 1, 'b': 1, 'y': 1}] +Here's the explanation of some special signals in signal vectors: +- "scenario": The "scenario" is not DUT's signal but to tell you the current scenario index. +- "check_en": The "check_en" signal is not from the DUT. "Check_en" is a bool value to tell you this is the time to check the output of DUT. It is related to the class method "check" (we will explain it later). After checking the output, a new scenario will start. +3. Your current task is: write a python class "GoldenDUT". This python class can represent the golden DUT (the ideal one). In your "GoldenDUT", you should do the following things: +- 3.1. write a method "def __init__(self)". Set the inner states/values of the golden DUT. These values have suffix "_reg". The initial value of these inner values is "x", but later will be digits. The "__init__" method has no input parameters except "self". +- 3.2. write a method "def load(self, signal_vector)". This method is to load the important input signals and the inner values of "GoldenDUT" shall change according to the input signals. There is no clock signal in the input signal vector, every time the "load" method is called, it means a new clock cycle. The initial values "x" should be changed according to the input signals. This method has no return value. +- 3.3. write a method "def check(self, signal_vector)". This method is to determine the expected output values and compare them with output signals from DUT. It should return True or False only. If return false, please print the error message. Hint: you can use code like "print(f"Scenario: {signal_vector['scenario']}, expected: a={a_reg}, observed a={a_observed}")" to print, suppose "a" is the output signal's name. +- 3.4. write other methods you need, they can be called by "load" or "check". +- 3.5. the input of "load" and "check" is the signal vector. The signal vector is a dictionary, the key is the signal name, the value is the signal value. +4. Other information: +- You can use binary (like 0x1101), hexadecimal (like 0x1a) or normal number format in python. +- if the bit width of one variable is limited, use bit mask to assure the correctness of the value. +- you can import numpy, math, scipy or other python libraries to help you write the python class. +5. You have the information below to help you check the correctness of DUT: + +DUT circuit problem description: +一个8位乘法器,输入两个8位无符号数,输出16位乘积 +The header of DUT (note the input and output signals): +module example(input clk, input rst, input [7:0] a, input [7:0] b, output [15:0] y); +Here is the basic rules in python for the module. It was generated in previous stage. You can use it as a reference, but you should write your own python script. This is just for your better understanding. You can use them or not in your python class +import numpy as np + +def multiply_8bit(a, b): + """ + This function multiplies two 8-bit unsigned integers and returns the 16-bit result. + + Parameters: + a (int): 8-bit unsigned integer (0x00 to 0xFF) + b (int): 8-bit unsigned integer (0x00 to 0xFF) + + Returns: + int: 16-bit unsigned integer (0x0000 to 0xFFFF) + """ + # Ensure inputs are 8-bit unsigned integers + a = a & 0xFF + b = b & 0xFF + + # Perform the multiplication + product = a * b + + # Ensure the result is 16-bit + product = product & 0xFFFF + + return product + +# Test scenarios +test_scenarios = [ + (0x00, 0x00), # Scenario 1 + (0x01, 0x01), # Scenario 2 + (0x00, 0xFF), # Scenario 3 + (0xFF, 0x00), # Scenario 4 + (0xFF, 0xFF), # Scenario 5 + (0x80, 0x80), # Scenario 6 + (0x01, 0x80), # Scenario 7 + (0x80, 0x01), # Scenario 8 + (0x0A, 0x0B), # Scenario 9 + (0x7F, 0x7F), # Scenario 10 + (0x0F, 0xF0), # Scenario 11 + (0x00, 0x00), # Scenario 12 + (0x0F, 0x0F), # Scenario 13 + (0x0E, 0x0E), # Scenario 14 + (0x0D, 0x0D) # Scenario 15 +] + +# Generate expected results for each test scenario +expected_results = [multiply_8bit(a, b) for a, b in test_scenarios] + +# Print the expected results +for i, (a, b) in enumerate(test_scenarios): + print(f"Scenario {i+1}: a = 0x{a:02X}, b = 0x{b:02X} -> Expected y = 0x{expected_results[i]:04X}") + + + +[IMPORTANT] +I will repeat the important information: +3. Your current task is: write a python class "GoldenDUT". This python class can represent the golden DUT (the ideal one). In your "GoldenDUT", you should do the following things: +- 3.1. write a method "def __init__(self)". Set the inner states/values of the golden DUT. These values have suffix "_reg". The initial value of these inner values should be digits. You can set the initial values according to information or just "0"s. The "__init__" method has no input parameters except "self". +- 3.2. write a method "def load(self, signal_vector)". This method is to load the important input signals and the inner values of "GoldenDUT" shall change according to the input signals. There is no clock signal in the input signal vector, every time the "load" method is called, it means a new clock cycle. The initial values "x" should be changed according to the input signals. This method has no return value. +- 3.3. write a method "def check(self, signal_vector)". This method is to determine the expected output values and compare them with output signals from DUT. It should return True or False only. If return false, please print the error message. Hint: you can use code like "print(f"Scenario: {signal_vector['scenario']}, expected: a={a_reg}, observed a={a_observed}")" to print, suppose "a" is the output signal's name. +- 3.4. write other methods you need, they can be called by "load" or "check". +- 3.5. the input of "load" and "check" is the signal vector. The signal vector is a dictionary, the key is the signal name, the value is the signal value. +4. Other information: +- You can use binary (like 0x1101), hexadecimal (like 0x1a) or normal number format in python. +- if the bit width of one variable is limited, use bit mask to assure the correctness of the value. +- you can import numpy, math, scipy or other python libraries to help you write the python class. + +please only reply the python codes of the python class. no other words. + + + +########## assistant ########## +```python +class GoldenDUT: + def __init__(self): + self.a_reg = 0 + self.b_reg = 0 + self.y_reg = 0 + self.rst_reg = 1 + + def load(self, signal_vector): + self.a_reg = signal_vector['a'] & 0xFF + self.b_reg = signal_vector['b'] & 0xFF + self.rst_reg = signal_vector['rst'] + if self.rst_reg == 1: + self.a_reg = 0 + self.b_reg = 0 + self.y_reg = 0 + else: + self.y_reg = self.multiply_8bit(self.a_reg, self.b_reg) + + def check(self, signal_vector): + expected_y = self.multiply_8bit(self.a_reg, self.b_reg) + observed_y = signal_vector['y'] & 0xFFFF + if expected_y != observed_y: + print(f"Scenario: {signal_vector['scenario']}, expected: y=0x{expected_y:04X}, observed: y=0x{observed_y:04X}") + return False + return True + + def multiply_8bit(self, a, b): + a = a & 0xFF + b = b & 0xFF + product = a * b + product = product & 0xFFFF + return product +``` + diff --git a/output/example_mul/example_mul/1_TBgen/stage_checklist.txt b/output/example_mul/example_mul/1_TBgen/stage_checklist.txt new file mode 100644 index 0000000..09967ff --- /dev/null +++ b/output/example_mul/example_mul/1_TBgen/stage_checklist.txt @@ -0,0 +1,2 @@ + +[SYSTEM PRECHECK] All scenarios are included in the testbench code. You can continue to the next stage. \ No newline at end of file diff --git a/output/example_mul/example_mul/3_TBeval/eval1_GoldenRTL/TBout.txt b/output/example_mul/example_mul/3_TBeval/eval1_GoldenRTL/TBout.txt new file mode 100644 index 0000000..d19bed3 --- /dev/null +++ b/output/example_mul/example_mul/3_TBeval/eval1_GoldenRTL/TBout.txt @@ -0,0 +1,30 @@ +scenario: 1, clk = 0, rst = 1, a = 0, b = 0, y = 0 +[check]scenario: 1, clk = 1, rst = 1, a = 0, b = 0, y = 0 +scenario: 2, clk = 1, rst = 0, a = 1, b = 1, y = 1 +[check]scenario: 2, clk = 1, rst = 0, a = 1, b = 1, y = 1 +scenario: 3, clk = 1, rst = 0, a = 0, b = 255, y = 0 +[check]scenario: 3, clk = 1, rst = 0, a = 0, b = 255, y = 0 +scenario: 4, clk = 1, rst = 0, a = 255, b = 0, y = 0 +[check]scenario: 4, clk = 1, rst = 0, a = 255, b = 0, y = 0 +scenario: 5, clk = 1, rst = 0, a = 255, b = 255, y = 65025 +[check]scenario: 5, clk = 1, rst = 0, a = 255, b = 255, y = 65025 +scenario: 6, clk = 1, rst = 0, a = 128, b = 128, y = 16384 +[check]scenario: 6, clk = 1, rst = 0, a = 128, b = 128, y = 16384 +scenario: 7, clk = 1, rst = 0, a = 1, b = 128, y = 128 +[check]scenario: 7, clk = 1, rst = 0, a = 1, b = 128, y = 128 +scenario: 8, clk = 1, rst = 0, a = 128, b = 1, y = 128 +[check]scenario: 8, clk = 1, rst = 0, a = 128, b = 1, y = 128 +scenario: 9, clk = 1, rst = 0, a = 10, b = 11, y = 110 +[check]scenario: 9, clk = 1, rst = 0, a = 10, b = 11, y = 110 +scenario: 10, clk = 1, rst = 0, a = 127, b = 127, y = 16129 +[check]scenario: 10, clk = 1, rst = 0, a = 127, b = 127, y = 16129 +scenario: 11, clk = 1, rst = 0, a = 15, b = 240, y = 3600 +[check]scenario: 11, clk = 1, rst = 0, a = 15, b = 240, y = 3600 +scenario: 12, clk = 1, rst = 0, a = 0, b = 0, y = 0 +[check]scenario: 12, clk = 1, rst = 0, a = 0, b = 0, y = 0 +scenario: 13, clk = 1, rst = 0, a = 15, b = 15, y = 225 +[check]scenario: 13, clk = 1, rst = 0, a = 15, b = 15, y = 225 +scenario: 14, clk = 1, rst = 0, a = 14, b = 14, y = 196 +[check]scenario: 14, clk = 1, rst = 0, a = 14, b = 14, y = 196 +scenario: 15, clk = 1, rst = 0, a = 13, b = 13, y = 169 +[check]scenario: 15, clk = 1, rst = 0, a = 13, b = 13, y = 169 diff --git a/output/example_mul/example_mul/3_TBeval/eval1_GoldenRTL/example_mul.v b/output/example_mul/example_mul/3_TBeval/eval1_GoldenRTL/example_mul.v new file mode 100644 index 0000000..823e27d --- /dev/null +++ b/output/example_mul/example_mul/3_TBeval/eval1_GoldenRTL/example_mul.v @@ -0,0 +1,10 @@ + +module example( + input clk, + input rst, + input [7:0] a, + input [7:0] b, + output [15:0] y +); + assign y = a * b; +endmodule diff --git a/output/example_mul/example_mul/3_TBeval/eval1_GoldenRTL/example_mul_tb.py b/output/example_mul/example_mul/3_TBeval/eval1_GoldenRTL/example_mul_tb.py new file mode 100644 index 0000000..34aa94a --- /dev/null +++ b/output/example_mul/example_mul/3_TBeval/eval1_GoldenRTL/example_mul_tb.py @@ -0,0 +1,82 @@ +class GoldenDUT: + def __init__(self): + self.a_reg = 0 + self.b_reg = 0 + self.y_reg = 0 + self.rst_reg = 1 + + def load(self, signal_vector): + self.a_reg = signal_vector['a'] & 0xFF + self.b_reg = signal_vector['b'] & 0xFF + self.rst_reg = signal_vector['rst'] + if self.rst_reg == 1: + self.a_reg = 0 + self.b_reg = 0 + self.y_reg = 0 + else: + self.y_reg = self.multiply_8bit(self.a_reg, self.b_reg) + + def check(self, signal_vector): + expected_y = self.multiply_8bit(self.a_reg, self.b_reg) + observed_y = signal_vector['y'] & 0xFFFF + if expected_y != observed_y: + print(f"Scenario: {signal_vector['scenario']}, expected: y=0x{expected_y:04X}, observed: y=0x{observed_y:04X}") + return False + return True + + def multiply_8bit(self, a, b): + a = a & 0xFF + b = b & 0xFF + product = a * b + product = product & 0xFFFF + return product + +def check_dut(vectors_in): + golden_dut = GoldenDUT() + failed_scenarios = [] + for vector in vectors_in: + if vector["check_en"]: + check_pass = golden_dut.check(vector) + if check_pass: + print(f"Passed; vector: {vector}") + else: + print(f"Failed; vector: {vector}") + failed_scenarios.append(vector["scenario"]) + golden_dut.load(vector) + return failed_scenarios + +def SignalTxt_to_dictlist(txt:str): + signals = [] + lines = txt.strip().split("\n") + for line in lines: + signal = {} + if line.startswith("[check]"): + signal["check_en"] = True + line = line[7:] + elif line.startswith("scenario"): + signal["check_en"] = False + else: + continue + line = line.strip().split(", ") + for item in line: + if "scenario" in item: + item = item.split(": ") + signal["scenario"] = item[1].replace(" ", "") + else: + item = item.split(" = ") + key = item[0] + value = item[1] + if ("x" not in value) and ("X" not in value) and ("z" not in value): + signal[key] = int(value) + else: + if ("x" in value) or ("X" in value): + signal[key] = 0 # used to be "x" + else: + signal[key] = 0 # used to be "z" + signals.append(signal) + return signals +with open("TBout.txt", "r") as f: + txt = f.read() +vectors_in = SignalTxt_to_dictlist(txt) +tb_pass = check_dut(vectors_in) +print(tb_pass) diff --git a/output/example_mul/example_mul/3_TBeval/eval1_GoldenRTL/example_mul_tb.v b/output/example_mul/example_mul/3_TBeval/eval1_GoldenRTL/example_mul_tb.v new file mode 100644 index 0000000..95e6081 --- /dev/null +++ b/output/example_mul/example_mul/3_TBeval/eval1_GoldenRTL/example_mul_tb.v @@ -0,0 +1,138 @@ +`timescale 1ns / 1ps +module testbench; +reg clk; +reg rst; +reg [7:0] a; +reg [7:0] b; +wire [15:0] y; + +integer file, scenario; +// DUT instantiation +example DUT ( + .clk(clk), + .rst(rst), + .a(a), + .b(b), + .y(y) +); +// Clock generation +initial begin + clk = 0; + forever #5 clk = ~clk; +end + +initial begin + file = $fopen("TBout.txt", "w"); +end +// Scenario Based Test +initial begin + // scenario 1 + scenario = 1; + rst = 1; a = 8'b0000_0000; b = 8'b0000_0000; + $fdisplay(file, "scenario: %d, clk = %d, rst = %d, a = %d, b = %d, y = %d", scenario, clk, rst, a, b, y); + #10; + $fdisplay(file, "[check]scenario: %d, clk = %d, rst = %d, a = %d, b = %d, y = %d", scenario, clk, rst, a, b, y); #10; + + // scenario 2 + scenario = 2; + rst = 0; a = 8'b0000_0001; b = 8'b0000_0001; + $fdisplay(file, "scenario: %d, clk = %d, rst = %d, a = %d, b = %d, y = %d", scenario, clk, rst, a, b, y); + #10; + $fdisplay(file, "[check]scenario: %d, clk = %d, rst = %d, a = %d, b = %d, y = %d", scenario, clk, rst, a, b, y); #10; + + // scenario 3 + scenario = 3; + a = 8'b0000_0000; b = 8'b1111_1111; + $fdisplay(file, "scenario: %d, clk = %d, rst = %d, a = %d, b = %d, y = %d", scenario, clk, rst, a, b, y); + #10; + $fdisplay(file, "[check]scenario: %d, clk = %d, rst = %d, a = %d, b = %d, y = %d", scenario, clk, rst, a, b, y); #10; + + // scenario 4 + scenario = 4; + a = 8'b1111_1111; b = 8'b0000_0000; + $fdisplay(file, "scenario: %d, clk = %d, rst = %d, a = %d, b = %d, y = %d", scenario, clk, rst, a, b, y); + #10; + $fdisplay(file, "[check]scenario: %d, clk = %d, rst = %d, a = %d, b = %d, y = %d", scenario, clk, rst, a, b, y); #10; + + // scenario 5 + scenario = 5; + a = 8'b1111_1111; b = 8'b1111_1111; + $fdisplay(file, "scenario: %d, clk = %d, rst = %d, a = %d, b = %d, y = %d", scenario, clk, rst, a, b, y); + #10; + $fdisplay(file, "[check]scenario: %d, clk = %d, rst = %d, a = %d, b = %d, y = %d", scenario, clk, rst, a, b, y); #10; + + // scenario 6 + scenario = 6; + a = 8'b1000_0000; b = 8'b1000_0000; + $fdisplay(file, "scenario: %d, clk = %d, rst = %d, a = %d, b = %d, y = %d", scenario, clk, rst, a, b, y); + #10; + $fdisplay(file, "[check]scenario: %d, clk = %d, rst = %d, a = %d, b = %d, y = %d", scenario, clk, rst, a, b, y); #10; + + // scenario 7 + scenario = 7; + a = 8'b0000_0001; b = 8'b1000_0000; + $fdisplay(file, "scenario: %d, clk = %d, rst = %d, a = %d, b = %d, y = %d", scenario, clk, rst, a, b, y); + #10; + $fdisplay(file, "[check]scenario: %d, clk = %d, rst = %d, a = %d, b = %d, y = %d", scenario, clk, rst, a, b, y); #10; + + // scenario 8 + scenario = 8; + a = 8'b1000_0000; b = 8'b0000_0001; + $fdisplay(file, "scenario: %d, clk = %d, rst = %d, a = %d, b = %d, y = %d", scenario, clk, rst, a, b, y); + #10; + $fdisplay(file, "[check]scenario: %d, clk = %d, rst = %d, a = %d, b = %d, y = %d", scenario, clk, rst, a, b, y); #10; + + // scenario 9 + scenario = 9; + a = 8'b0000_1010; b = 8'b0000_1011; + $fdisplay(file, "scenario: %d, clk = %d, rst = %d, a = %d, b = %d, y = %d", scenario, clk, rst, a, b, y); + #10; + $fdisplay(file, "[check]scenario: %d, clk = %d, rst = %d, a = %d, b = %d, y = %d", scenario, clk, rst, a, b, y); #10; + + // scenario 10 + scenario = 10; + a = 8'b0111_1111; b = 8'b0111_1111; + $fdisplay(file, "scenario: %d, clk = %d, rst = %d, a = %d, b = %d, y = %d", scenario, clk, rst, a, b, y); + #10; + $fdisplay(file, "[check]scenario: %d, clk = %d, rst = %d, a = %d, b = %d, y = %d", scenario, clk, rst, a, b, y); #10; + + // scenario 11 + scenario = 11; + a = 8'b0000_1111; b = 8'b1111_0000; + $fdisplay(file, "scenario: %d, clk = %d, rst = %d, a = %d, b = %d, y = %d", scenario, clk, rst, a, b, y); + #10; + $fdisplay(file, "[check]scenario: %d, clk = %d, rst = %d, a = %d, b = %d, y = %d", scenario, clk, rst, a, b, y); #10; + + // scenario 12 + scenario = 12; + a = 8'b0000_0000; b = 8'b0000_0000; + $fdisplay(file, "scenario: %d, clk = %d, rst = %d, a = %d, b = %d, y = %d", scenario, clk, rst, a, b, y); + #10; + $fdisplay(file, "[check]scenario: %d, clk = %d, rst = %d, a = %d, b = %d, y = %d", scenario, clk, rst, a, b, y); #10; + + // scenario 13 + scenario = 13; + a = 8'b0000_1111; b = 8'b0000_1111; + $fdisplay(file, "scenario: %d, clk = %d, rst = %d, a = %d, b = %d, y = %d", scenario, clk, rst, a, b, y); + #10; + $fdisplay(file, "[check]scenario: %d, clk = %d, rst = %d, a = %d, b = %d, y = %d", scenario, clk, rst, a, b, y); #10; + + // scenario 14 + scenario = 14; + a = 8'b0000_1110; b = 8'b0000_1110; + $fdisplay(file, "scenario: %d, clk = %d, rst = %d, a = %d, b = %d, y = %d", scenario, clk, rst, a, b, y); + #10; + $fdisplay(file, "[check]scenario: %d, clk = %d, rst = %d, a = %d, b = %d, y = %d", scenario, clk, rst, a, b, y); #10; + + // scenario 15 + scenario = 15; + a = 8'b0000_1101; b = 8'b0000_1101; + $fdisplay(file, "scenario: %d, clk = %d, rst = %d, a = %d, b = %d, y = %d", scenario, clk, rst, a, b, y); + #10; + $fdisplay(file, "[check]scenario: %d, clk = %d, rst = %d, a = %d, b = %d, y = %d", scenario, clk, rst, a, b, y); + + $fclose(file); + $finish; +end + +endmodule diff --git a/output/example_mul/example_mul/3_TBeval/eval1_GoldenRTL/run.vvp b/output/example_mul/example_mul/3_TBeval/eval1_GoldenRTL/run.vvp new file mode 100644 index 0000000..3e7f86a --- /dev/null +++ b/output/example_mul/example_mul/3_TBeval/eval1_GoldenRTL/run.vvp @@ -0,0 +1,226 @@ +#! /usr/bin/vvp +:ivl_version "12.0 (stable)"; +:ivl_delay_selection "TYPICAL"; +:vpi_time_precision - 12; +:vpi_module "/usr/lib/x86_64-linux-gnu/ivl/system.vpi"; +:vpi_module "/usr/lib/x86_64-linux-gnu/ivl/vhdl_sys.vpi"; +:vpi_module "/usr/lib/x86_64-linux-gnu/ivl/vhdl_textio.vpi"; +:vpi_module "/usr/lib/x86_64-linux-gnu/ivl/v2005_math.vpi"; +:vpi_module "/usr/lib/x86_64-linux-gnu/ivl/va_math.vpi"; +:vpi_module "/usr/lib/x86_64-linux-gnu/ivl/v2009.vpi"; +S_0x5cd5353522d0 .scope package, "$unit" "$unit" 2 1; + .timescale 0 0; +S_0x5cd535352460 .scope module, "testbench" "testbench" 3 2; + .timescale -9 -12; +v0x5cd535391920_0 .var "a", 7 0; +v0x5cd535391a00_0 .var "b", 7 0; +v0x5cd535391ad0_0 .var "clk", 0 0; +v0x5cd535391bd0_0 .var/i "file", 31 0; +v0x5cd535391c70_0 .var "rst", 0 0; +v0x5cd535391d10_0 .var/i "scenario", 31 0; +v0x5cd535391db0_0 .net "y", 15 0, L_0x5cd535392170; 1 drivers +S_0x5cd535318490 .scope module, "DUT" "example" 3 11, 4 2 0, S_0x5cd535352460; + .timescale 0 0; + .port_info 0 /INPUT 1 "clk"; + .port_info 1 /INPUT 1 "rst"; + .port_info 2 /INPUT 8 "a"; + .port_info 3 /INPUT 8 "b"; + .port_info 4 /OUTPUT 16 "y"; +v0x5cd535368690_0 .net *"_ivl_0", 15 0, L_0x5cd535391ea0; 1 drivers +L_0x7475544ce018 .functor BUFT 1, C4<00000000>, C4<0>, C4<0>, C4<0>; +v0x5cd535391160_0 .net *"_ivl_3", 7 0, L_0x7475544ce018; 1 drivers +v0x5cd535391240_0 .net *"_ivl_4", 15 0, L_0x5cd535392010; 1 drivers +L_0x7475544ce060 .functor BUFT 1, C4<00000000>, C4<0>, C4<0>, C4<0>; +v0x5cd535391330_0 .net *"_ivl_7", 7 0, L_0x7475544ce060; 1 drivers +v0x5cd535391410_0 .net "a", 7 0, v0x5cd535391920_0; 1 drivers +v0x5cd535391540_0 .net "b", 7 0, v0x5cd535391a00_0; 1 drivers +v0x5cd535391620_0 .net "clk", 0 0, v0x5cd535391ad0_0; 1 drivers +v0x5cd5353916e0_0 .net "rst", 0 0, v0x5cd535391c70_0; 1 drivers +v0x5cd5353917a0_0 .net "y", 15 0, L_0x5cd535392170; alias, 1 drivers +L_0x5cd535391ea0 .concat [ 8 8 0 0], v0x5cd535391920_0, L_0x7475544ce018; +L_0x5cd535392010 .concat [ 8 8 0 0], v0x5cd535391a00_0, L_0x7475544ce060; +L_0x5cd535392170 .arith/mult 16, L_0x5cd535391ea0, L_0x5cd535392010; + .scope S_0x5cd535352460; +T_0 ; + %pushi/vec4 0, 0, 1; + %store/vec4 v0x5cd535391ad0_0, 0, 1; +T_0.0 ; + %delay 5000, 0; + %load/vec4 v0x5cd535391ad0_0; + %inv; + %store/vec4 v0x5cd535391ad0_0, 0, 1; + %jmp T_0.0; + %end; + .thread T_0; + .scope S_0x5cd535352460; +T_1 ; + %vpi_func 3 25 "$fopen" 32, "TBout.txt", "w" {0 0 0}; + %store/vec4 v0x5cd535391bd0_0, 0, 32; + %end; + .thread T_1; + .scope S_0x5cd535352460; +T_2 ; + %pushi/vec4 1, 0, 32; + %store/vec4 v0x5cd535391d10_0, 0, 32; + %pushi/vec4 1, 0, 1; + %store/vec4 v0x5cd535391c70_0, 0, 1; + %pushi/vec4 0, 0, 8; + %store/vec4 v0x5cd535391920_0, 0, 8; + %pushi/vec4 0, 0, 8; + %store/vec4 v0x5cd535391a00_0, 0, 8; + %vpi_call/w 3 32 "$fdisplay", v0x5cd535391bd0_0, "scenario: %d, clk = %d, rst = %d, a = %d, b = %d, y = %d", v0x5cd535391d10_0, v0x5cd535391ad0_0, v0x5cd535391c70_0, v0x5cd535391920_0, v0x5cd535391a00_0, v0x5cd535391db0_0 {0 0 0}; + %delay 10000, 0; + %vpi_call/w 3 34 "$fdisplay", v0x5cd535391bd0_0, "[check]scenario: %d, clk = %d, rst = %d, a = %d, b = %d, y = %d", v0x5cd535391d10_0, v0x5cd535391ad0_0, v0x5cd535391c70_0, v0x5cd535391920_0, v0x5cd535391a00_0, v0x5cd535391db0_0 {0 0 0}; + %delay 10000, 0; + %pushi/vec4 2, 0, 32; + %store/vec4 v0x5cd535391d10_0, 0, 32; + %pushi/vec4 0, 0, 1; + %store/vec4 v0x5cd535391c70_0, 0, 1; + %pushi/vec4 1, 0, 8; + %store/vec4 v0x5cd535391920_0, 0, 8; + %pushi/vec4 1, 0, 8; + %store/vec4 v0x5cd535391a00_0, 0, 8; + %vpi_call/w 3 39 "$fdisplay", v0x5cd535391bd0_0, "scenario: %d, clk = %d, rst = %d, a = %d, b = %d, y = %d", v0x5cd535391d10_0, v0x5cd535391ad0_0, v0x5cd535391c70_0, v0x5cd535391920_0, v0x5cd535391a00_0, v0x5cd535391db0_0 {0 0 0}; + %delay 10000, 0; + %vpi_call/w 3 41 "$fdisplay", v0x5cd535391bd0_0, "[check]scenario: %d, clk = %d, rst = %d, a = %d, b = %d, y = %d", v0x5cd535391d10_0, v0x5cd535391ad0_0, v0x5cd535391c70_0, v0x5cd535391920_0, v0x5cd535391a00_0, v0x5cd535391db0_0 {0 0 0}; + %delay 10000, 0; + %pushi/vec4 3, 0, 32; + %store/vec4 v0x5cd535391d10_0, 0, 32; + %pushi/vec4 0, 0, 8; + %store/vec4 v0x5cd535391920_0, 0, 8; + %pushi/vec4 255, 0, 8; + %store/vec4 v0x5cd535391a00_0, 0, 8; + %vpi_call/w 3 46 "$fdisplay", v0x5cd535391bd0_0, "scenario: %d, clk = %d, rst = %d, a = %d, b = %d, y = %d", v0x5cd535391d10_0, v0x5cd535391ad0_0, v0x5cd535391c70_0, v0x5cd535391920_0, v0x5cd535391a00_0, v0x5cd535391db0_0 {0 0 0}; + %delay 10000, 0; + %vpi_call/w 3 48 "$fdisplay", v0x5cd535391bd0_0, "[check]scenario: %d, clk = %d, rst = %d, a = %d, b = %d, y = %d", v0x5cd535391d10_0, v0x5cd535391ad0_0, v0x5cd535391c70_0, v0x5cd535391920_0, v0x5cd535391a00_0, v0x5cd535391db0_0 {0 0 0}; + %delay 10000, 0; + %pushi/vec4 4, 0, 32; + %store/vec4 v0x5cd535391d10_0, 0, 32; + %pushi/vec4 255, 0, 8; + %store/vec4 v0x5cd535391920_0, 0, 8; + %pushi/vec4 0, 0, 8; + %store/vec4 v0x5cd535391a00_0, 0, 8; + %vpi_call/w 3 53 "$fdisplay", v0x5cd535391bd0_0, "scenario: %d, clk = %d, rst = %d, a = %d, b = %d, y = %d", v0x5cd535391d10_0, v0x5cd535391ad0_0, v0x5cd535391c70_0, v0x5cd535391920_0, v0x5cd535391a00_0, v0x5cd535391db0_0 {0 0 0}; + %delay 10000, 0; + %vpi_call/w 3 55 "$fdisplay", v0x5cd535391bd0_0, "[check]scenario: %d, clk = %d, rst = %d, a = %d, b = %d, y = %d", v0x5cd535391d10_0, v0x5cd535391ad0_0, v0x5cd535391c70_0, v0x5cd535391920_0, v0x5cd535391a00_0, v0x5cd535391db0_0 {0 0 0}; + %delay 10000, 0; + %pushi/vec4 5, 0, 32; + %store/vec4 v0x5cd535391d10_0, 0, 32; + %pushi/vec4 255, 0, 8; + %store/vec4 v0x5cd535391920_0, 0, 8; + %pushi/vec4 255, 0, 8; + %store/vec4 v0x5cd535391a00_0, 0, 8; + %vpi_call/w 3 60 "$fdisplay", v0x5cd535391bd0_0, "scenario: %d, clk = %d, rst = %d, a = %d, b = %d, y = %d", v0x5cd535391d10_0, v0x5cd535391ad0_0, v0x5cd535391c70_0, v0x5cd535391920_0, v0x5cd535391a00_0, v0x5cd535391db0_0 {0 0 0}; + %delay 10000, 0; + %vpi_call/w 3 62 "$fdisplay", v0x5cd535391bd0_0, "[check]scenario: %d, clk = %d, rst = %d, a = %d, b = %d, y = %d", v0x5cd535391d10_0, v0x5cd535391ad0_0, v0x5cd535391c70_0, v0x5cd535391920_0, v0x5cd535391a00_0, v0x5cd535391db0_0 {0 0 0}; + %delay 10000, 0; + %pushi/vec4 6, 0, 32; + %store/vec4 v0x5cd535391d10_0, 0, 32; + %pushi/vec4 128, 0, 8; + %store/vec4 v0x5cd535391920_0, 0, 8; + %pushi/vec4 128, 0, 8; + %store/vec4 v0x5cd535391a00_0, 0, 8; + %vpi_call/w 3 67 "$fdisplay", v0x5cd535391bd0_0, "scenario: %d, clk = %d, rst = %d, a = %d, b = %d, y = %d", v0x5cd535391d10_0, v0x5cd535391ad0_0, v0x5cd535391c70_0, v0x5cd535391920_0, v0x5cd535391a00_0, v0x5cd535391db0_0 {0 0 0}; + %delay 10000, 0; + %vpi_call/w 3 69 "$fdisplay", v0x5cd535391bd0_0, "[check]scenario: %d, clk = %d, rst = %d, a = %d, b = %d, y = %d", v0x5cd535391d10_0, v0x5cd535391ad0_0, v0x5cd535391c70_0, v0x5cd535391920_0, v0x5cd535391a00_0, v0x5cd535391db0_0 {0 0 0}; + %delay 10000, 0; + %pushi/vec4 7, 0, 32; + %store/vec4 v0x5cd535391d10_0, 0, 32; + %pushi/vec4 1, 0, 8; + %store/vec4 v0x5cd535391920_0, 0, 8; + %pushi/vec4 128, 0, 8; + %store/vec4 v0x5cd535391a00_0, 0, 8; + %vpi_call/w 3 74 "$fdisplay", v0x5cd535391bd0_0, "scenario: %d, clk = %d, rst = %d, a = %d, b = %d, y = %d", v0x5cd535391d10_0, v0x5cd535391ad0_0, v0x5cd535391c70_0, v0x5cd535391920_0, v0x5cd535391a00_0, v0x5cd535391db0_0 {0 0 0}; + %delay 10000, 0; + %vpi_call/w 3 76 "$fdisplay", v0x5cd535391bd0_0, "[check]scenario: %d, clk = %d, rst = %d, a = %d, b = %d, y = %d", v0x5cd535391d10_0, v0x5cd535391ad0_0, v0x5cd535391c70_0, v0x5cd535391920_0, v0x5cd535391a00_0, v0x5cd535391db0_0 {0 0 0}; + %delay 10000, 0; + %pushi/vec4 8, 0, 32; + %store/vec4 v0x5cd535391d10_0, 0, 32; + %pushi/vec4 128, 0, 8; + %store/vec4 v0x5cd535391920_0, 0, 8; + %pushi/vec4 1, 0, 8; + %store/vec4 v0x5cd535391a00_0, 0, 8; + %vpi_call/w 3 81 "$fdisplay", v0x5cd535391bd0_0, "scenario: %d, clk = %d, rst = %d, a = %d, b = %d, y = %d", v0x5cd535391d10_0, v0x5cd535391ad0_0, v0x5cd535391c70_0, v0x5cd535391920_0, v0x5cd535391a00_0, v0x5cd535391db0_0 {0 0 0}; + %delay 10000, 0; + %vpi_call/w 3 83 "$fdisplay", v0x5cd535391bd0_0, "[check]scenario: %d, clk = %d, rst = %d, a = %d, b = %d, y = %d", v0x5cd535391d10_0, v0x5cd535391ad0_0, v0x5cd535391c70_0, v0x5cd535391920_0, v0x5cd535391a00_0, v0x5cd535391db0_0 {0 0 0}; + %delay 10000, 0; + %pushi/vec4 9, 0, 32; + %store/vec4 v0x5cd535391d10_0, 0, 32; + %pushi/vec4 10, 0, 8; + %store/vec4 v0x5cd535391920_0, 0, 8; + %pushi/vec4 11, 0, 8; + %store/vec4 v0x5cd535391a00_0, 0, 8; + %vpi_call/w 3 88 "$fdisplay", v0x5cd535391bd0_0, "scenario: %d, clk = %d, rst = %d, a = %d, b = %d, y = %d", v0x5cd535391d10_0, v0x5cd535391ad0_0, v0x5cd535391c70_0, v0x5cd535391920_0, v0x5cd535391a00_0, v0x5cd535391db0_0 {0 0 0}; + %delay 10000, 0; + %vpi_call/w 3 90 "$fdisplay", v0x5cd535391bd0_0, "[check]scenario: %d, clk = %d, rst = %d, a = %d, b = %d, y = %d", v0x5cd535391d10_0, v0x5cd535391ad0_0, v0x5cd535391c70_0, v0x5cd535391920_0, v0x5cd535391a00_0, v0x5cd535391db0_0 {0 0 0}; + %delay 10000, 0; + %pushi/vec4 10, 0, 32; + %store/vec4 v0x5cd535391d10_0, 0, 32; + %pushi/vec4 127, 0, 8; + %store/vec4 v0x5cd535391920_0, 0, 8; + %pushi/vec4 127, 0, 8; + %store/vec4 v0x5cd535391a00_0, 0, 8; + %vpi_call/w 3 95 "$fdisplay", v0x5cd535391bd0_0, "scenario: %d, clk = %d, rst = %d, a = %d, b = %d, y = %d", v0x5cd535391d10_0, v0x5cd535391ad0_0, v0x5cd535391c70_0, v0x5cd535391920_0, v0x5cd535391a00_0, v0x5cd535391db0_0 {0 0 0}; + %delay 10000, 0; + %vpi_call/w 3 97 "$fdisplay", v0x5cd535391bd0_0, "[check]scenario: %d, clk = %d, rst = %d, a = %d, b = %d, y = %d", v0x5cd535391d10_0, v0x5cd535391ad0_0, v0x5cd535391c70_0, v0x5cd535391920_0, v0x5cd535391a00_0, v0x5cd535391db0_0 {0 0 0}; + %delay 10000, 0; + %pushi/vec4 11, 0, 32; + %store/vec4 v0x5cd535391d10_0, 0, 32; + %pushi/vec4 15, 0, 8; + %store/vec4 v0x5cd535391920_0, 0, 8; + %pushi/vec4 240, 0, 8; + %store/vec4 v0x5cd535391a00_0, 0, 8; + %vpi_call/w 3 102 "$fdisplay", v0x5cd535391bd0_0, "scenario: %d, clk = %d, rst = %d, a = %d, b = %d, y = %d", v0x5cd535391d10_0, v0x5cd535391ad0_0, v0x5cd535391c70_0, v0x5cd535391920_0, v0x5cd535391a00_0, v0x5cd535391db0_0 {0 0 0}; + %delay 10000, 0; + %vpi_call/w 3 104 "$fdisplay", v0x5cd535391bd0_0, "[check]scenario: %d, clk = %d, rst = %d, a = %d, b = %d, y = %d", v0x5cd535391d10_0, v0x5cd535391ad0_0, v0x5cd535391c70_0, v0x5cd535391920_0, v0x5cd535391a00_0, v0x5cd535391db0_0 {0 0 0}; + %delay 10000, 0; + %pushi/vec4 12, 0, 32; + %store/vec4 v0x5cd535391d10_0, 0, 32; + %pushi/vec4 0, 0, 8; + %store/vec4 v0x5cd535391920_0, 0, 8; + %pushi/vec4 0, 0, 8; + %store/vec4 v0x5cd535391a00_0, 0, 8; + %vpi_call/w 3 109 "$fdisplay", v0x5cd535391bd0_0, "scenario: %d, clk = %d, rst = %d, a = %d, b = %d, y = %d", v0x5cd535391d10_0, v0x5cd535391ad0_0, v0x5cd535391c70_0, v0x5cd535391920_0, v0x5cd535391a00_0, v0x5cd535391db0_0 {0 0 0}; + %delay 10000, 0; + %vpi_call/w 3 111 "$fdisplay", v0x5cd535391bd0_0, "[check]scenario: %d, clk = %d, rst = %d, a = %d, b = %d, y = %d", v0x5cd535391d10_0, v0x5cd535391ad0_0, v0x5cd535391c70_0, v0x5cd535391920_0, v0x5cd535391a00_0, v0x5cd535391db0_0 {0 0 0}; + %delay 10000, 0; + %pushi/vec4 13, 0, 32; + %store/vec4 v0x5cd535391d10_0, 0, 32; + %pushi/vec4 15, 0, 8; + %store/vec4 v0x5cd535391920_0, 0, 8; + %pushi/vec4 15, 0, 8; + %store/vec4 v0x5cd535391a00_0, 0, 8; + %vpi_call/w 3 116 "$fdisplay", v0x5cd535391bd0_0, "scenario: %d, clk = %d, rst = %d, a = %d, b = %d, y = %d", v0x5cd535391d10_0, v0x5cd535391ad0_0, v0x5cd535391c70_0, v0x5cd535391920_0, v0x5cd535391a00_0, v0x5cd535391db0_0 {0 0 0}; + %delay 10000, 0; + %vpi_call/w 3 118 "$fdisplay", v0x5cd535391bd0_0, "[check]scenario: %d, clk = %d, rst = %d, a = %d, b = %d, y = %d", v0x5cd535391d10_0, v0x5cd535391ad0_0, v0x5cd535391c70_0, v0x5cd535391920_0, v0x5cd535391a00_0, v0x5cd535391db0_0 {0 0 0}; + %delay 10000, 0; + %pushi/vec4 14, 0, 32; + %store/vec4 v0x5cd535391d10_0, 0, 32; + %pushi/vec4 14, 0, 8; + %store/vec4 v0x5cd535391920_0, 0, 8; + %pushi/vec4 14, 0, 8; + %store/vec4 v0x5cd535391a00_0, 0, 8; + %vpi_call/w 3 123 "$fdisplay", v0x5cd535391bd0_0, "scenario: %d, clk = %d, rst = %d, a = %d, b = %d, y = %d", v0x5cd535391d10_0, v0x5cd535391ad0_0, v0x5cd535391c70_0, v0x5cd535391920_0, v0x5cd535391a00_0, v0x5cd535391db0_0 {0 0 0}; + %delay 10000, 0; + %vpi_call/w 3 125 "$fdisplay", v0x5cd535391bd0_0, "[check]scenario: %d, clk = %d, rst = %d, a = %d, b = %d, y = %d", v0x5cd535391d10_0, v0x5cd535391ad0_0, v0x5cd535391c70_0, v0x5cd535391920_0, v0x5cd535391a00_0, v0x5cd535391db0_0 {0 0 0}; + %delay 10000, 0; + %pushi/vec4 15, 0, 32; + %store/vec4 v0x5cd535391d10_0, 0, 32; + %pushi/vec4 13, 0, 8; + %store/vec4 v0x5cd535391920_0, 0, 8; + %pushi/vec4 13, 0, 8; + %store/vec4 v0x5cd535391a00_0, 0, 8; + %vpi_call/w 3 130 "$fdisplay", v0x5cd535391bd0_0, "scenario: %d, clk = %d, rst = %d, a = %d, b = %d, y = %d", v0x5cd535391d10_0, v0x5cd535391ad0_0, v0x5cd535391c70_0, v0x5cd535391920_0, v0x5cd535391a00_0, v0x5cd535391db0_0 {0 0 0}; + %delay 10000, 0; + %vpi_call/w 3 132 "$fdisplay", v0x5cd535391bd0_0, "[check]scenario: %d, clk = %d, rst = %d, a = %d, b = %d, y = %d", v0x5cd535391d10_0, v0x5cd535391ad0_0, v0x5cd535391c70_0, v0x5cd535391920_0, v0x5cd535391a00_0, v0x5cd535391db0_0 {0 0 0}; + %vpi_call/w 3 134 "$fclose", v0x5cd535391bd0_0 {0 0 0}; + %vpi_call/w 3 135 "$finish" {0 0 0}; + %end; + .thread T_2; +# The file index is used to find the file name in the following table. +:file_names 5; + "N/A"; + ""; + "-"; + "example_mul_tb.v"; + "example_mul.v"; diff --git a/output/example_mul/example_mul/3_TBeval/eval1_GoldenRTL/run_info.txt b/output/example_mul/example_mul/3_TBeval/eval1_GoldenRTL/run_info.txt new file mode 100644 index 0000000..e5749f8 --- /dev/null +++ b/output/example_mul/example_mul/3_TBeval/eval1_GoldenRTL/run_info.txt @@ -0,0 +1,15 @@ +iverilog simulation passed! + +iverilog cmd 1: +/usr/bin/iverilog -g2012 -o run.vvp example_mul.v example_mul_tb.v +iverilog cmd 1 output: + +iverilog cmd 1 error: + +iverilog cmd 2: +/usr/bin/vvp run.vvp +iverilog cmd 2 output: +example_mul_tb.v:135: $finish called at 290000 (1ps) + +iverilog cmd 2 error: + diff --git a/output/example_mul/example_mul/3_TBeval/eval1_GoldenRTL/run_info_py.txt b/output/example_mul/example_mul/3_TBeval/eval1_GoldenRTL/run_info_py.txt new file mode 100644 index 0000000..8fb03a3 --- /dev/null +++ b/output/example_mul/example_mul/3_TBeval/eval1_GoldenRTL/run_info_py.txt @@ -0,0 +1,24 @@ +python compilation passed! + +###output: +Passed; vector: {'check_en': True, 'scenario': '1', 'clk': 1, 'rst': 1, 'a': 0, 'b': 0, 'y': 0} +Passed; vector: {'check_en': True, 'scenario': '2', 'clk': 1, 'rst': 0, 'a': 1, 'b': 1, 'y': 1} +Passed; vector: {'check_en': True, 'scenario': '3', 'clk': 1, 'rst': 0, 'a': 0, 'b': 255, 'y': 0} +Passed; vector: {'check_en': True, 'scenario': '4', 'clk': 1, 'rst': 0, 'a': 255, 'b': 0, 'y': 0} +Passed; vector: {'check_en': True, 'scenario': '5', 'clk': 1, 'rst': 0, 'a': 255, 'b': 255, 'y': 65025} +Passed; vector: {'check_en': True, 'scenario': '6', 'clk': 1, 'rst': 0, 'a': 128, 'b': 128, 'y': 16384} +Passed; vector: {'check_en': True, 'scenario': '7', 'clk': 1, 'rst': 0, 'a': 1, 'b': 128, 'y': 128} +Passed; vector: {'check_en': True, 'scenario': '8', 'clk': 1, 'rst': 0, 'a': 128, 'b': 1, 'y': 128} +Passed; vector: {'check_en': True, 'scenario': '9', 'clk': 1, 'rst': 0, 'a': 10, 'b': 11, 'y': 110} +Passed; vector: {'check_en': True, 'scenario': '10', 'clk': 1, 'rst': 0, 'a': 127, 'b': 127, 'y': 16129} +Passed; vector: {'check_en': True, 'scenario': '11', 'clk': 1, 'rst': 0, 'a': 15, 'b': 240, 'y': 3600} +Passed; vector: {'check_en': True, 'scenario': '12', 'clk': 1, 'rst': 0, 'a': 0, 'b': 0, 'y': 0} +Passed; vector: {'check_en': True, 'scenario': '13', 'clk': 1, 'rst': 0, 'a': 15, 'b': 15, 'y': 225} +Passed; vector: {'check_en': True, 'scenario': '14', 'clk': 1, 'rst': 0, 'a': 14, 'b': 14, 'y': 196} +Passed; vector: {'check_en': True, 'scenario': '15', 'clk': 1, 'rst': 0, 'a': 13, 'b': 13, 'y': 169} +[] + + +###error: + + diff --git a/output/example_mul/example_mul/3_TBeval/eval1_GoldenRTL/vlist.txt b/output/example_mul/example_mul/3_TBeval/eval1_GoldenRTL/vlist.txt new file mode 100644 index 0000000..ed079c9 --- /dev/null +++ b/output/example_mul/example_mul/3_TBeval/eval1_GoldenRTL/vlist.txt @@ -0,0 +1,2 @@ +example_mul.v +example_mul_tb.v diff --git a/output/example_mul/example_mul/DUT.v b/output/example_mul/example_mul/DUT.v new file mode 100644 index 0000000..823e27d --- /dev/null +++ b/output/example_mul/example_mul/DUT.v @@ -0,0 +1,10 @@ + +module example( + input clk, + input rst, + input [7:0] a, + input [7:0] b, + output [15:0] y +); + assign y = a * b; +endmodule diff --git a/output/example_mul/example_mul/final_TB.py b/output/example_mul/example_mul/final_TB.py new file mode 100644 index 0000000..34aa94a --- /dev/null +++ b/output/example_mul/example_mul/final_TB.py @@ -0,0 +1,82 @@ +class GoldenDUT: + def __init__(self): + self.a_reg = 0 + self.b_reg = 0 + self.y_reg = 0 + self.rst_reg = 1 + + def load(self, signal_vector): + self.a_reg = signal_vector['a'] & 0xFF + self.b_reg = signal_vector['b'] & 0xFF + self.rst_reg = signal_vector['rst'] + if self.rst_reg == 1: + self.a_reg = 0 + self.b_reg = 0 + self.y_reg = 0 + else: + self.y_reg = self.multiply_8bit(self.a_reg, self.b_reg) + + def check(self, signal_vector): + expected_y = self.multiply_8bit(self.a_reg, self.b_reg) + observed_y = signal_vector['y'] & 0xFFFF + if expected_y != observed_y: + print(f"Scenario: {signal_vector['scenario']}, expected: y=0x{expected_y:04X}, observed: y=0x{observed_y:04X}") + return False + return True + + def multiply_8bit(self, a, b): + a = a & 0xFF + b = b & 0xFF + product = a * b + product = product & 0xFFFF + return product + +def check_dut(vectors_in): + golden_dut = GoldenDUT() + failed_scenarios = [] + for vector in vectors_in: + if vector["check_en"]: + check_pass = golden_dut.check(vector) + if check_pass: + print(f"Passed; vector: {vector}") + else: + print(f"Failed; vector: {vector}") + failed_scenarios.append(vector["scenario"]) + golden_dut.load(vector) + return failed_scenarios + +def SignalTxt_to_dictlist(txt:str): + signals = [] + lines = txt.strip().split("\n") + for line in lines: + signal = {} + if line.startswith("[check]"): + signal["check_en"] = True + line = line[7:] + elif line.startswith("scenario"): + signal["check_en"] = False + else: + continue + line = line.strip().split(", ") + for item in line: + if "scenario" in item: + item = item.split(": ") + signal["scenario"] = item[1].replace(" ", "") + else: + item = item.split(" = ") + key = item[0] + value = item[1] + if ("x" not in value) and ("X" not in value) and ("z" not in value): + signal[key] = int(value) + else: + if ("x" in value) or ("X" in value): + signal[key] = 0 # used to be "x" + else: + signal[key] = 0 # used to be "z" + signals.append(signal) + return signals +with open("TBout.txt", "r") as f: + txt = f.read() +vectors_in = SignalTxt_to_dictlist(txt) +tb_pass = check_dut(vectors_in) +print(tb_pass) diff --git a/output/example_mul/example_mul/final_TB.v b/output/example_mul/example_mul/final_TB.v new file mode 100644 index 0000000..95e6081 --- /dev/null +++ b/output/example_mul/example_mul/final_TB.v @@ -0,0 +1,138 @@ +`timescale 1ns / 1ps +module testbench; +reg clk; +reg rst; +reg [7:0] a; +reg [7:0] b; +wire [15:0] y; + +integer file, scenario; +// DUT instantiation +example DUT ( + .clk(clk), + .rst(rst), + .a(a), + .b(b), + .y(y) +); +// Clock generation +initial begin + clk = 0; + forever #5 clk = ~clk; +end + +initial begin + file = $fopen("TBout.txt", "w"); +end +// Scenario Based Test +initial begin + // scenario 1 + scenario = 1; + rst = 1; a = 8'b0000_0000; b = 8'b0000_0000; + $fdisplay(file, "scenario: %d, clk = %d, rst = %d, a = %d, b = %d, y = %d", scenario, clk, rst, a, b, y); + #10; + $fdisplay(file, "[check]scenario: %d, clk = %d, rst = %d, a = %d, b = %d, y = %d", scenario, clk, rst, a, b, y); #10; + + // scenario 2 + scenario = 2; + rst = 0; a = 8'b0000_0001; b = 8'b0000_0001; + $fdisplay(file, "scenario: %d, clk = %d, rst = %d, a = %d, b = %d, y = %d", scenario, clk, rst, a, b, y); + #10; + $fdisplay(file, "[check]scenario: %d, clk = %d, rst = %d, a = %d, b = %d, y = %d", scenario, clk, rst, a, b, y); #10; + + // scenario 3 + scenario = 3; + a = 8'b0000_0000; b = 8'b1111_1111; + $fdisplay(file, "scenario: %d, clk = %d, rst = %d, a = %d, b = %d, y = %d", scenario, clk, rst, a, b, y); + #10; + $fdisplay(file, "[check]scenario: %d, clk = %d, rst = %d, a = %d, b = %d, y = %d", scenario, clk, rst, a, b, y); #10; + + // scenario 4 + scenario = 4; + a = 8'b1111_1111; b = 8'b0000_0000; + $fdisplay(file, "scenario: %d, clk = %d, rst = %d, a = %d, b = %d, y = %d", scenario, clk, rst, a, b, y); + #10; + $fdisplay(file, "[check]scenario: %d, clk = %d, rst = %d, a = %d, b = %d, y = %d", scenario, clk, rst, a, b, y); #10; + + // scenario 5 + scenario = 5; + a = 8'b1111_1111; b = 8'b1111_1111; + $fdisplay(file, "scenario: %d, clk = %d, rst = %d, a = %d, b = %d, y = %d", scenario, clk, rst, a, b, y); + #10; + $fdisplay(file, "[check]scenario: %d, clk = %d, rst = %d, a = %d, b = %d, y = %d", scenario, clk, rst, a, b, y); #10; + + // scenario 6 + scenario = 6; + a = 8'b1000_0000; b = 8'b1000_0000; + $fdisplay(file, "scenario: %d, clk = %d, rst = %d, a = %d, b = %d, y = %d", scenario, clk, rst, a, b, y); + #10; + $fdisplay(file, "[check]scenario: %d, clk = %d, rst = %d, a = %d, b = %d, y = %d", scenario, clk, rst, a, b, y); #10; + + // scenario 7 + scenario = 7; + a = 8'b0000_0001; b = 8'b1000_0000; + $fdisplay(file, "scenario: %d, clk = %d, rst = %d, a = %d, b = %d, y = %d", scenario, clk, rst, a, b, y); + #10; + $fdisplay(file, "[check]scenario: %d, clk = %d, rst = %d, a = %d, b = %d, y = %d", scenario, clk, rst, a, b, y); #10; + + // scenario 8 + scenario = 8; + a = 8'b1000_0000; b = 8'b0000_0001; + $fdisplay(file, "scenario: %d, clk = %d, rst = %d, a = %d, b = %d, y = %d", scenario, clk, rst, a, b, y); + #10; + $fdisplay(file, "[check]scenario: %d, clk = %d, rst = %d, a = %d, b = %d, y = %d", scenario, clk, rst, a, b, y); #10; + + // scenario 9 + scenario = 9; + a = 8'b0000_1010; b = 8'b0000_1011; + $fdisplay(file, "scenario: %d, clk = %d, rst = %d, a = %d, b = %d, y = %d", scenario, clk, rst, a, b, y); + #10; + $fdisplay(file, "[check]scenario: %d, clk = %d, rst = %d, a = %d, b = %d, y = %d", scenario, clk, rst, a, b, y); #10; + + // scenario 10 + scenario = 10; + a = 8'b0111_1111; b = 8'b0111_1111; + $fdisplay(file, "scenario: %d, clk = %d, rst = %d, a = %d, b = %d, y = %d", scenario, clk, rst, a, b, y); + #10; + $fdisplay(file, "[check]scenario: %d, clk = %d, rst = %d, a = %d, b = %d, y = %d", scenario, clk, rst, a, b, y); #10; + + // scenario 11 + scenario = 11; + a = 8'b0000_1111; b = 8'b1111_0000; + $fdisplay(file, "scenario: %d, clk = %d, rst = %d, a = %d, b = %d, y = %d", scenario, clk, rst, a, b, y); + #10; + $fdisplay(file, "[check]scenario: %d, clk = %d, rst = %d, a = %d, b = %d, y = %d", scenario, clk, rst, a, b, y); #10; + + // scenario 12 + scenario = 12; + a = 8'b0000_0000; b = 8'b0000_0000; + $fdisplay(file, "scenario: %d, clk = %d, rst = %d, a = %d, b = %d, y = %d", scenario, clk, rst, a, b, y); + #10; + $fdisplay(file, "[check]scenario: %d, clk = %d, rst = %d, a = %d, b = %d, y = %d", scenario, clk, rst, a, b, y); #10; + + // scenario 13 + scenario = 13; + a = 8'b0000_1111; b = 8'b0000_1111; + $fdisplay(file, "scenario: %d, clk = %d, rst = %d, a = %d, b = %d, y = %d", scenario, clk, rst, a, b, y); + #10; + $fdisplay(file, "[check]scenario: %d, clk = %d, rst = %d, a = %d, b = %d, y = %d", scenario, clk, rst, a, b, y); #10; + + // scenario 14 + scenario = 14; + a = 8'b0000_1110; b = 8'b0000_1110; + $fdisplay(file, "scenario: %d, clk = %d, rst = %d, a = %d, b = %d, y = %d", scenario, clk, rst, a, b, y); + #10; + $fdisplay(file, "[check]scenario: %d, clk = %d, rst = %d, a = %d, b = %d, y = %d", scenario, clk, rst, a, b, y); #10; + + // scenario 15 + scenario = 15; + a = 8'b0000_1101; b = 8'b0000_1101; + $fdisplay(file, "scenario: %d, clk = %d, rst = %d, a = %d, b = %d, y = %d", scenario, clk, rst, a, b, y); + #10; + $fdisplay(file, "[check]scenario: %d, clk = %d, rst = %d, a = %d, b = %d, y = %d", scenario, clk, rst, a, b, y); + + $fclose(file); + $finish; +end + +endmodule diff --git a/output/example_mul/example_mul/run_info.json b/output/example_mul/example_mul/run_info.json new file mode 100644 index 0000000..3c1f0e6 --- /dev/null +++ b/output/example_mul/example_mul/run_info.json @@ -0,0 +1,29 @@ +{ + "task_id": "example_mul", + "task_number": 1, + "time": 372.62, + "prompt_tokens": 8593, + "completion_tokens": 6050, + "token_cost": 0.5348599999999999, + "ERROR(incomplete)": false, + "op_record": [ + "gen", + "syncheck", + "eval" + ], + "reboot_times": 0, + "max_iter": 10, + "coverage": 0.0, + "circuit_type": "SEQ", + "checklist_worked": false, + "scenario_num": 15, + "Eval0_pass": true, + "Eval0_iv_pass": true, + "debug_iter_iv": 0, + "iv_runing_time": 0.03, + "Eval0_py_pass": true, + "debug_iter_py": 0, + "py_runing_time": 0.02, + "Eval1_pass": true, + "full_pass": null +} \ No newline at end of file diff --git a/output/example_mul/example_mul/run_info_short.json b/output/example_mul/example_mul/run_info_short.json new file mode 100644 index 0000000..8ecffde --- /dev/null +++ b/output/example_mul/example_mul/run_info_short.json @@ -0,0 +1,8 @@ +{ + "task_id": "example_mul", + "eval_progress": "Eval1 - passed", + "TB_corrected": null, + "reboot_times": 0, + "time": 372.62, + "cost": 0.5348599999999999 +} \ No newline at end of file diff --git a/output/example_mul/example_mul/task_log.log b/output/example_mul/example_mul/task_log.log new file mode 100644 index 0000000..2250c80 --- /dev/null +++ b/output/example_mul/example_mul/task_log.log @@ -0,0 +1,16 @@ +2026-03-30 15:50:24 | INFO | [example_mul] [TBgen] stage_0 ends (5.68s used) +2026-03-30 15:50:48 | INFO | [example_mul] [TBgen] stage_1 ends (24.62s used) +2026-03-30 15:51:23 | INFO | [example_mul] [TBgen] stage_2 ends (34.70s used) +2026-03-30 15:51:53 | INFO | [example_mul] [TBgen] stage_3 ends (29.57s used) +2026-03-30 15:53:48 | INFO | [example_mul] [TBgen] stage_4 ends (115.37s used) +2026-03-30 15:53:48 | INFO | [example_mul] [TBgen] stage_checklist ends (0.00s used) +2026-03-30 15:56:14 | INFO | [example_mul] [TBgen] stage_4b ends (146.02s used) +2026-03-30 15:56:31 | INFO | [example_mul] [TBgen] stage_5 ends (16.58s used) +2026-03-30 15:56:31 | INFO | [example_mul] +2026-03-30 15:56:31 | INFO | [example_mul] [TBsim] iverilog compilation : passed! +2026-03-30 15:56:31 | INFO | [example_mul] [TBsim] python simulation : passed! +2026-03-30 15:56:31 | INFO | [example_mul] [TBsim] TBsim finished : True! +2026-03-30 15:56:31 | INFO | [example_mul] +2026-03-30 15:56:31 | INFO | [example_mul] [TBeval] Eval 1: Golden RTL checking begins +2026-03-30 15:56:31 | POSITIVE | [example_mul] [TBeval] Eval 1: Golden RTL checking passed! +2026-03-30 15:56:31 | INFO | [example_mul] diff --git a/output/example_mul_tb.v b/output/example_mul_tb.v new file mode 100644 index 0000000..95e6081 --- /dev/null +++ b/output/example_mul_tb.v @@ -0,0 +1,138 @@ +`timescale 1ns / 1ps +module testbench; +reg clk; +reg rst; +reg [7:0] a; +reg [7:0] b; +wire [15:0] y; + +integer file, scenario; +// DUT instantiation +example DUT ( + .clk(clk), + .rst(rst), + .a(a), + .b(b), + .y(y) +); +// Clock generation +initial begin + clk = 0; + forever #5 clk = ~clk; +end + +initial begin + file = $fopen("TBout.txt", "w"); +end +// Scenario Based Test +initial begin + // scenario 1 + scenario = 1; + rst = 1; a = 8'b0000_0000; b = 8'b0000_0000; + $fdisplay(file, "scenario: %d, clk = %d, rst = %d, a = %d, b = %d, y = %d", scenario, clk, rst, a, b, y); + #10; + $fdisplay(file, "[check]scenario: %d, clk = %d, rst = %d, a = %d, b = %d, y = %d", scenario, clk, rst, a, b, y); #10; + + // scenario 2 + scenario = 2; + rst = 0; a = 8'b0000_0001; b = 8'b0000_0001; + $fdisplay(file, "scenario: %d, clk = %d, rst = %d, a = %d, b = %d, y = %d", scenario, clk, rst, a, b, y); + #10; + $fdisplay(file, "[check]scenario: %d, clk = %d, rst = %d, a = %d, b = %d, y = %d", scenario, clk, rst, a, b, y); #10; + + // scenario 3 + scenario = 3; + a = 8'b0000_0000; b = 8'b1111_1111; + $fdisplay(file, "scenario: %d, clk = %d, rst = %d, a = %d, b = %d, y = %d", scenario, clk, rst, a, b, y); + #10; + $fdisplay(file, "[check]scenario: %d, clk = %d, rst = %d, a = %d, b = %d, y = %d", scenario, clk, rst, a, b, y); #10; + + // scenario 4 + scenario = 4; + a = 8'b1111_1111; b = 8'b0000_0000; + $fdisplay(file, "scenario: %d, clk = %d, rst = %d, a = %d, b = %d, y = %d", scenario, clk, rst, a, b, y); + #10; + $fdisplay(file, "[check]scenario: %d, clk = %d, rst = %d, a = %d, b = %d, y = %d", scenario, clk, rst, a, b, y); #10; + + // scenario 5 + scenario = 5; + a = 8'b1111_1111; b = 8'b1111_1111; + $fdisplay(file, "scenario: %d, clk = %d, rst = %d, a = %d, b = %d, y = %d", scenario, clk, rst, a, b, y); + #10; + $fdisplay(file, "[check]scenario: %d, clk = %d, rst = %d, a = %d, b = %d, y = %d", scenario, clk, rst, a, b, y); #10; + + // scenario 6 + scenario = 6; + a = 8'b1000_0000; b = 8'b1000_0000; + $fdisplay(file, "scenario: %d, clk = %d, rst = %d, a = %d, b = %d, y = %d", scenario, clk, rst, a, b, y); + #10; + $fdisplay(file, "[check]scenario: %d, clk = %d, rst = %d, a = %d, b = %d, y = %d", scenario, clk, rst, a, b, y); #10; + + // scenario 7 + scenario = 7; + a = 8'b0000_0001; b = 8'b1000_0000; + $fdisplay(file, "scenario: %d, clk = %d, rst = %d, a = %d, b = %d, y = %d", scenario, clk, rst, a, b, y); + #10; + $fdisplay(file, "[check]scenario: %d, clk = %d, rst = %d, a = %d, b = %d, y = %d", scenario, clk, rst, a, b, y); #10; + + // scenario 8 + scenario = 8; + a = 8'b1000_0000; b = 8'b0000_0001; + $fdisplay(file, "scenario: %d, clk = %d, rst = %d, a = %d, b = %d, y = %d", scenario, clk, rst, a, b, y); + #10; + $fdisplay(file, "[check]scenario: %d, clk = %d, rst = %d, a = %d, b = %d, y = %d", scenario, clk, rst, a, b, y); #10; + + // scenario 9 + scenario = 9; + a = 8'b0000_1010; b = 8'b0000_1011; + $fdisplay(file, "scenario: %d, clk = %d, rst = %d, a = %d, b = %d, y = %d", scenario, clk, rst, a, b, y); + #10; + $fdisplay(file, "[check]scenario: %d, clk = %d, rst = %d, a = %d, b = %d, y = %d", scenario, clk, rst, a, b, y); #10; + + // scenario 10 + scenario = 10; + a = 8'b0111_1111; b = 8'b0111_1111; + $fdisplay(file, "scenario: %d, clk = %d, rst = %d, a = %d, b = %d, y = %d", scenario, clk, rst, a, b, y); + #10; + $fdisplay(file, "[check]scenario: %d, clk = %d, rst = %d, a = %d, b = %d, y = %d", scenario, clk, rst, a, b, y); #10; + + // scenario 11 + scenario = 11; + a = 8'b0000_1111; b = 8'b1111_0000; + $fdisplay(file, "scenario: %d, clk = %d, rst = %d, a = %d, b = %d, y = %d", scenario, clk, rst, a, b, y); + #10; + $fdisplay(file, "[check]scenario: %d, clk = %d, rst = %d, a = %d, b = %d, y = %d", scenario, clk, rst, a, b, y); #10; + + // scenario 12 + scenario = 12; + a = 8'b0000_0000; b = 8'b0000_0000; + $fdisplay(file, "scenario: %d, clk = %d, rst = %d, a = %d, b = %d, y = %d", scenario, clk, rst, a, b, y); + #10; + $fdisplay(file, "[check]scenario: %d, clk = %d, rst = %d, a = %d, b = %d, y = %d", scenario, clk, rst, a, b, y); #10; + + // scenario 13 + scenario = 13; + a = 8'b0000_1111; b = 8'b0000_1111; + $fdisplay(file, "scenario: %d, clk = %d, rst = %d, a = %d, b = %d, y = %d", scenario, clk, rst, a, b, y); + #10; + $fdisplay(file, "[check]scenario: %d, clk = %d, rst = %d, a = %d, b = %d, y = %d", scenario, clk, rst, a, b, y); #10; + + // scenario 14 + scenario = 14; + a = 8'b0000_1110; b = 8'b0000_1110; + $fdisplay(file, "scenario: %d, clk = %d, rst = %d, a = %d, b = %d, y = %d", scenario, clk, rst, a, b, y); + #10; + $fdisplay(file, "[check]scenario: %d, clk = %d, rst = %d, a = %d, b = %d, y = %d", scenario, clk, rst, a, b, y); #10; + + // scenario 15 + scenario = 15; + a = 8'b0000_1101; b = 8'b0000_1101; + $fdisplay(file, "scenario: %d, clk = %d, rst = %d, a = %d, b = %d, y = %d", scenario, clk, rst, a, b, y); + #10; + $fdisplay(file, "[check]scenario: %d, clk = %d, rst = %d, a = %d, b = %d, y = %d", scenario, clk, rst, a, b, y); + + $fclose(file); + $finish; +end + +endmodule diff --git a/prompt_scripts/__init__.py b/prompt_scripts/__init__.py new file mode 100644 index 0000000..1cb6fd4 --- /dev/null +++ b/prompt_scripts/__init__.py @@ -0,0 +1,21 @@ +""" +Description : description +Author : Ruidi Qiu (r.qiu@tum.de) +Time : 2024/3/23 11:30:00 +LastEdited : 2024/7/24 11:43:13 +""" + +from .base_script import BaseScript, BaseScriptStage +from .script_pychecker import WF_pychecker +from .script_directgen import WF_directgen + +SCRIPTS_SELECTER = { + "pychecker": WF_pychecker, + "directgen": WF_directgen +} + +def get_script(script_name:str) -> BaseScript: + if script_name in SCRIPTS_SELECTER: + return SCRIPTS_SELECTER[script_name] + else: + raise ValueError(f"script name {script_name} is not supported") \ No newline at end of file diff --git a/prompt_scripts/__pycache__/__init__.cpython-312.pyc b/prompt_scripts/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..e4e11bff58976e0e97691e23527f1e5e07396492 GIT binary patch literal 879 zcmYLH&1(}u6rb6TY_e&Sw20b+x~F2abkigv#40v5t(H=16BPHd#GNr&%zijKD>PIP z@#wt;6?#`3h=vF`7CVw78*(g5orI$p3Itfj3X(qvMiM1(3Mp84CZXh8 z$xwGwdA0jay@vyf*gmyASF326h9+2D32ECSOv#Abc0wE3+Cw|ku9C$Uc6gVD&LYBj zwP8Cr_8K}C3&pZt(u*Z+X{k~wR0;)kvl-ADvMEM`Sh34Zy;$Zh@2zLcqr`PJLCd`d=SAc#`+?J@I;WI^e+S8ji2Be`}zqmd--_g(upjd6M7)mS0#8K Q9+dikpNEyd@fipI5AZVT9{>OV literal 0 HcmV?d00001 diff --git a/prompt_scripts/__pycache__/base_script.cpython-312.pyc b/prompt_scripts/__pycache__/base_script.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d246eeeeea328f524304e51c968d6102707d27ce GIT binary patch literal 15890 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 HIJN%=EMu{9 literal 0 HcmV?d00001 diff --git a/prompt_scripts/__pycache__/script_directgen.cpython-312.pyc b/prompt_scripts/__pycache__/script_directgen.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ee13fbd146554fa3c3cf651a4f55461917895799 GIT binary patch literal 3872 zcmbtXO>7&-72aJg$t9_u+O<TPxhe!EVw{>^s)C|qEZ~PUlXKlKl4yXGROBzn{Epy51DEJVtv|#&UU~ai%ajmi$zPE$ITdt!>Ue{8=AAHRZRGN~j( zLKP~PB2-~2jj$*rFjk2zOBI<$@AQ@8G=rh5a8{8-tsD^|6v_C$wu#=31E2l6}lp5%qXyXvC! zKFAm4MP)IppDZm%tMat2_))jnVBFUX!?G>UF!ZRex~x{=8o1zapd^mx%TE|axn{bq zVYs*&hP?EADmU-cS*|s2+SS}Shhym{nO&aG6(`{c7!!>~E-1Yb90oTR%-!v08w-2@ z9JUVjCMDYm2Sx4yAYU#?A8Y-0<7--a<}`Zz|iuKghq4e<6b`-qr4WEy|DEeld-_5BRI99~0*&FY@LmstxbPEt6MW ze^6A`a3ISjUK3s6gJ9vu1V8U$ScLF>$opp9yul39rUq}?MsOrrWJh9l(Lhso1jv%~ z&wbxoQ6CK4A6VP>`lDli%C2VNV>z~|^?jrbt!YCmKiSZZ@%^A%?oCISwi-zO@3bi| zNSm@KQ~8e}!eH|B$aGPc{RC%oj^i23=2&Yz5~L_W*<7=VXN9MM?cx!Ka~I_5wy4nR__<4c=6kIP}Cc$1w*`bUazr{$|tksC+ z5z{4^^FJHf693_|mhaIM0Y>SDd=I2L$Zq&6~i2^+AGIS7UXs3?KqK ziZ^e1qzcV}xd4n!%51M2D;i-iK@_m8PT=MUU-Qhwb)Aa%B|^$3%^^r-685)%ds!ft zu3Wo%y*M#lERahTf;&Ku7%S|@g%ukBcPBz-kPLU;Y1SyAmfNVA3nU}VcA7An$qJA5 z0CiK54G#k6YZIZScetL=EZdS-4AjPQK^Dw)1oBml1nC{Qw3CVR6?NyVzMY_d^f z5ImudJ>n708Z`(4_QlC9hf|lZ+hqogxUQE^}xpmj>42^Pn(B_m(>j|9Uyj?Gw^_`VTrbX30!#D;lAN#FaUo$ zNwVSRKFuuBzo0S_gK)xq_eSLA_QAu#5x{R`6rNhaU4|!Ar^? zG$}ROud12_D1bMVV+#+CB=$3C9{9)*&7<8fQ=(r6cCG#Gli9=jF6#dt$da`AQW_3n zs{3jvKA62fyRMCV(s@UGI9yoQ-Ut=ehjZ)NXpi*tx^^a%t`r}|6)Zknp3ru8 z3(yGt1h5iscgj!y4FmpN^s(C>H_M*D318#PZ8)~ecnNOxs(kJPmkJ9>DT8CHN$<>oj4*flS?k|&T=?hS({>{TjAG~${tu9o+ zyFSL5*W~uJ9_L3ef}=&wP72`1tEKk7e+djJ3c literal 0 HcmV?d00001 diff --git a/prompt_scripts/__pycache__/script_pychecker.cpython-312.pyc b/prompt_scripts/__pycache__/script_pychecker.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b8dbdecdee56ed555cd727f6a94e15bda75a5183 GIT binary patch literal 23383 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 Yzvyb#;+h|HyINhFE=4~PaN`5{A2Hd1P5=M^ literal 0 HcmV?d00001 diff --git a/prompt_scripts/__pycache__/script_pychecker_CMB_new.cpython-312.pyc b/prompt_scripts/__pycache__/script_pychecker_CMB_new.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..89c6ea50d8a00cda8743439ea27e1a93b34b9377 GIT binary patch literal 17802 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 J?Bd4Q^FR5nSOfq7 literal 0 HcmV?d00001 diff --git a/prompt_scripts/__pycache__/script_pychecker_SEQ.cpython-312.pyc b/prompt_scripts/__pycache__/script_pychecker_SEQ.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a8909b3bf7a040a752048a3e8660a8b5d0c0a723 GIT binary patch literal 32877 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 literal 0 HcmV?d00001 diff --git a/prompt_scripts/__pycache__/utils.cpython-312.pyc b/prompt_scripts/__pycache__/utils.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..097615aba8c4a3551e221c1b6ada2460c81dfa44 GIT binary patch literal 28704 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 R@wR_@Vr9RLGb?_G{~sxF#Q*>R literal 0 HcmV?d00001 diff --git a/prompt_scripts/base_script.py b/prompt_scripts/base_script.py new file mode 100644 index 0000000..4e7f541 --- /dev/null +++ b/prompt_scripts/base_script.py @@ -0,0 +1,319 @@ +""" +Description : the base script for prompt scripts +Author : Ruidi Qiu (r.qiu@tum.de) +Time : 2024/3/22 10:59:34 +LastEdited : 2024/8/12 23:34:35 +""" + +from LLM_call import llm_call, extract_code, message_to_conversation +from utils.utils import Timer, get_time +from loader_saver import autologger +import os +import loader_saver as ls +import copy + +DEFAULT_SYSMESSAGE = "You are the strongest AI in the world. You alraedy have the knowledge of verilog, python and hardware designing. Do not save words by discarding information. I will tip you 200$ if you can fullfill the tasks I give you." + +IDENTIFIER = { + "tb_start" : "```verilog", + "tb_end" : "```" +} + +TESTBENCH_TEMPLATE = """%s +`timescale 1ns / 1ps +(more verilog testbench code here...) +endmodule +%s""" % (IDENTIFIER["tb_start"], IDENTIFIER["tb_end"]) + +__all__ = ["BaseScriptStage", "BaseScript"] + +class BaseScriptStage: + """ + - the base stage for prompt scripts + - the functions that triggered when running: + - make_prompt: make the prompt for gpt (must be implemented) + - call_gpt: call gpt + - postprocessing: postprocessing the response (default is empty) + - gptkwargs: the kwargs for llm_call + - gpt_model: the model name + - api_key_path: the path of gpt key + - sysmessage: (can be ignored) the system message + - json_mode: (can be ignored) the json mode + - temperature: (can be ignored) the temperature + """ + def __init__(self, stage_name, **gptkwargs) -> None: + self.stage_name = stage_name + self.gpt_model = gptkwargs["gpt_model"] + self.api_key_path = gptkwargs["api_key_path"] + self.system_message = gptkwargs.get("system_message", DEFAULT_SYSMESSAGE) + self.json_mode = gptkwargs.get("json_mode", None) + self.temperature = gptkwargs.get("temperature", None) + self.time = 0.0 + self.prompt = "" + self.response = "" + self.print_message = "" + self.gptinfo = {} + self.conversation_message = "" + self.conversation_file_suffix = ".txt" + self.reboot = False + self.circuit_type = None # "CMB" or "SEQ"; pychecker will use this; you should set it in make_and_run_stages; + + @property + def will_gen_TB(self): + if hasattr(self, "TB_code_out"): + return True + else: + return False + + @property + def will_gen_Pychecker(self): + if hasattr(self, "Pychecker_code_out"): + return True + else: + return False + + def __call__(self, *args, **kwargs): + with Timer(print_en=False) as t: + self.run(*args, **kwargs) + self.time = t.interval + self.record() + pass + + def run(self): + self.make_prompt() + self.call_gpt() + self.postprocessing() + + def make_prompt(self): + raise NotImplementedError + + def call_gpt(self): + """ + actually it should be call_llm, but I dont want to modify the old code + """ + gpt_messages = [{"role": "user", "content": self.prompt}] + other_kwargs = {} + if self.temperature is not None: + other_kwargs["temperature"] = self.temperature + if self.json_mode is not None: + other_kwargs["json_mode"] = self.json_mode + self.response, self.gptinfo = llm_call(input_messages=gpt_messages, model=self.gpt_model, api_key_path=self.api_key_path, system_message=self.system_message, **other_kwargs) + self.conversation_message += message_to_conversation(self.gptinfo["messages"]) + + def postprocessing(self): + """empty function""" + pass + + def record(self): + self.print_message = "%s ends (%.2fs used)" % (self.stage_name, self.time) + + # tools + def save_log(self, config:object): + ls.save_log_line(self.print_message, config) + + def save_conversation(self, save_dir:str): + """This function will save the conversation to a file in save_dir. It will be called in stage_operation of BaseScript""" + if save_dir.endswith("/"): + save_dir = save_dir[:-1] + file_name = self.stage_name + self.conversation_file_suffix + path = os.path.join(save_dir, file_name) + with open(path, "w") as f: + f.write(self.conversation_message) + + # def reboot(self): + # self.reboot_en = True + # self.conversation_message = "" + # self.time = 0.0 + # self.prompt = "" + # self.response = "" + # self.print_message = "" + # self.gptinfo = {} + # self.conversation_message = "" + # self.conversation_file_suffix = ".txt" + # # TODO in your script, you should also reset the custom attributes + + def extract_code(self, text, code_type): + """ + #### function: + - extract code from text + #### input: + - text: str, gpt's response + - code_type: str, like "verilog" + #### output: + - list of found code blocks + """ + return extract_code(text=text, code_type=code_type) + + def update_tokens(self, tokens): + tokens["prompt"] += self.gptinfo.get("usage", {}).get("prompt_tokens", 0) # in case gpt has not been called + tokens["completion"] += self.gptinfo.get("usage", {}).get("completion_tokens", 0) + return tokens + + def add_prompt_line(self, prompt): + self.prompt += prompt + "\n" + +class BaseScript: + """ + the base class for prompt scripts + - the functions that triggered when running: + - make_and_run_stages: make and run stages (must be implemented) + - postprocessing: postprocessing the response (default is empty) + - save_codes: save the generated codes + """ + def __init__(self, prob_data:dict, task_dir:str, config:object) -> None: + self.stages = [] + self.task_dir = task_dir if not task_dir.endswith("/") else task_dir[:-1] + self.config = config + self.gptkwargs = { + "gpt_model": self.config.gpt.model, + "api_key_path": self.config.gpt.key_path + } + self.prob_data = prob_data + self.TB_code = "" + self.TB_code_dir = os.path.join(self.task_dir, "TBgen_codes") # pychecker codes will be saved in the same directory + self.TB_code_name = prob_data["task_id"] + "_tb.v" + self.Pychecker_code = "" # only for pychecker scripts + self.Pychecker_code_name = prob_data["task_id"] + "_tb.py" + self.empty_DUT_name = prob_data["task_id"] + ".v" + self.empty_DUT = prob_data["header"] + "\n\nendmodule\n" + self.stages_gencode = [] # includes all the stages that generate code, will be used in rebooting generation; see more in stage operation + self.tokens = {"prompt": 0, "completion": 0} + self.time = 0.0 + self.reboot_idx = -1 # start from 0; -1 means no reboot. the reboot index will be increased by 1 after each reboot + self.reboot_stages = [] #[reboot_stages_iter_0, reboot_stages_iter_1, ...] + self.reboot_mode = "TB" # "TB" or "PY"; modified by run_reboot; checked in make_and_run_reboot_stages if needed + self.py_debug_focus = False # if True, when debug python, will only send the upper part (no check_dut); specific for pychecker + self.checklist_worked = False # if True, the scenario checklist did help our work. This is a flag for further analysis + os.makedirs(self.TB_code_dir, exist_ok=True) + self.scenario_num = None + self.scenario_dict = None + + @property + def Pychecker_en(self): + if self.Pychecker_code != "": + return True + else: + return False + + @property + def Pychecker_code_dir(self): + return self.TB_code_dir + + def __call__(self, *args, **kwargs): + self.run(*args, **kwargs) + + def run(self): + self.make_and_run_stages() + self.compute_time_tokens() + self.postprocessing() + self.save_codes() + + def make_and_run_stages(self): + """ + - in this function, you should make stages and run them + - for example: + :: + + stage1 = Stage1(**kwargs) + self.stage_operation(stage1) + """ + raise NotImplementedError("No make_and_run_stages: You should implement this function in your own script") + + def make_and_run_reboot_stages(self, debug_dir): + raise NotImplementedError("No reboot settings: You should implement this function in your own script") + + def postprocessing(self): + """empty function""" + pass + + def save_codes(self, codes_dir:str=None): + if codes_dir is None: + codes_dir = self.TB_code_dir + os.makedirs(codes_dir, exist_ok=True) + TB_code_path = os.path.join(codes_dir, self.TB_code_name) + with open(TB_code_path, "w") as f: + f.write(self.TB_code) + empty_DUT_path = os.path.join(codes_dir, self.empty_DUT_name) + with open(empty_DUT_path, "w") as f: + f.write(self.empty_DUT) + # save pychecker code if have + if self.Pychecker_en: + Pychecker_code_path = os.path.join(codes_dir, self.Pychecker_code_name) + with open(Pychecker_code_path, "w") as f: + f.write(self.Pychecker_code) + + def stage_operation(self, stage:BaseScriptStage, conversation_dir:str=None, reboot_en:bool=False): + """ + - what to do on a stage after making it; will be called in make_and_run_stages + - run, save stages and renew the generated codes of current wf + """ + if conversation_dir is None: + conversation_dir = self.task_dir + stage() + if reboot_en: + self.reboot_stages[self.reboot_idx].append(stage) + else: + self.stages.append(stage) + stage.save_conversation(conversation_dir) + stage.save_log(self.config) + if stage.will_gen_TB: + self.TB_code = stage.TB_code_out + if stage.will_gen_Pychecker: + self.Pychecker_code = stage.Pychecker_code_out + # for checklist: + if hasattr(stage, "TB_modified"): # this attr is in checklist_stage + self.checklist_worked = stage.TB_modified + # will automaticly add stages to self.stages_gencode + # the rule is: this stage will generate TB or its previous stage is in self.stages_gencode; also, this stage is not reboot stage + # if (stage.will_gen_TB or (len(self.stages_gencode) > 0 and self.stages_gencode[-1] in self.stages)) and not stage.reboot_en: + # self.stages_gencode.append(stage) + + def run_reboot(self, debug_dir, reboot_mode="TB"): + """ + - regenerate the TB code + """ + self.reboot_idx += 1 + self.reboot_stages.append([]) + self.TB_code = "" + self.reboot_mode = reboot_mode # this will be checked in make_and_run_reboot_stages if needed + debug_dir = debug_dir[:-1] if debug_dir.endswith("/") else debug_dir + # will be discarded by 28/04/2024 + # make and run stages + # for stage in self.stages_gencode: + # new_stage = copy.deepcopy(stage) + # new_stage.stage_name = stage.stage_name + "_reboot" + str(self.reboot_idx) + # # new_stage.reboot() + # new_stage.reboot_en = True + # self.stage_operation(new_stage, debug_dir) + # reboot_stages_in_this_iter.append(new_stage) + self.make_and_run_reboot_stages(debug_dir) + # postprocessing + self.compute_time_tokens(self.reboot_stages[self.reboot_idx]) + self.postprocessing() + # save codes + self.save_codes(debug_dir) + + def clear_time_tokens(self): + self.time = 0.0 + self.tokens = {"prompt": 0, "completion": 0} + + def compute_time_tokens(self, stages=None): + if stages is None: + stages = self.stages + for stage in stages: + self.time += stage.time + self.tokens = stage.update_tokens(self.tokens) + + def save_log(self, line): + # ls.save_log_line(line, self.config) + autologger.info(line) + + def __call__(self, *args, **kwargs): + self.run(*args, **kwargs) + pass + + def get_attr(self, attr_name:str): + if hasattr(self, attr_name): + return getattr(self, attr_name) + else: + return None \ No newline at end of file diff --git a/prompt_scripts/legacy/__pycache__/script_RTLchecker0306.cpython-312.pyc b/prompt_scripts/legacy/__pycache__/script_RTLchecker0306.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..9d33d6e8148c80f64f1c29e89c8b94d2b6d6c2a8 GIT binary patch literal 21956 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 None: + gptkwargs["json_mode"] = True + super().__init__("stage_2", **gptkwargs) + self.prob_data = prob_data + self.response_stage1 = response_stage1 + self.txt1 = STAGE2_TXT1 + self.txt2 = STAGE2_TXT2 + + def make_prompt(self): + self.prompt = "" + self.add_prompt_line(self.txt1) + # problem description + self.add_prompt_line("RTL circuit problem description:") + self.add_prompt_line(self.prob_data["description"]) + # specification + self.add_prompt_line("RTL testbench specification:") + self.add_prompt_line(self.response_stage1) + # DUT header + self.add_prompt_line("DUT header:") + self.add_prompt_line(self.prob_data["header"]) + # template + self.add_prompt_line(self.txt2) + + +STAGE3_TXT1="""1. Your task is to write a verilog testbench for an verilog RTL module code (we call it as "DUT", device under test). The information we have is the problem description that guides student to write the RTL code (DUT) and the header of the "DUT". Our target is to generate the verilog testbench for the DUT. This testbench can check if the DUT in verilog satisfies all technical requirements of the problem description. +2. you are in section 3; in this section, please give me the rules of an ideal DUT. you should give these rules in python. (For convenience, you can use binary or hexadecimal format in python, i.e. 0b0010 and 0x1a). Later we will use these ideal rules to generate expected values in each test scenario. currently you must only generate the rules. the input of these rules should be related to the test vectors from test scenario. the rule should give the expected values under test vectors. +3. your information is:""" + +class Stage3(BaseScriptStage): + def __init__(self, prob_data, response_stage1, response_stage2, **gptkwargs) -> None: + super().__init__("stage_3", **gptkwargs) + self.prob_data = prob_data + self.response_stage1 = response_stage1 + self.response_stage2 = response_stage2 + self.txt1 = STAGE3_TXT1 + + def make_prompt(self): + self.prompt = "" + self.add_prompt_line(self.txt1) + # problem description + self.add_prompt_line("RTL circuit problem description:") + self.add_prompt_line(self.prob_data["description"]) + # specification + self.add_prompt_line("RTL testbench specification:") + self.add_prompt_line(self.response_stage1) + # DUT header + self.add_prompt_line("DUT header:") + self.add_prompt_line(self.prob_data["header"]) + # test scenarios + self.add_prompt_line("test scenario: (please note the test vectors below, it will help you determine the input parameters of the rules)") + self.add_prompt_line(self.response_stage2) + # end + self.add_prompt_line("your response should only contain python code. For convenience, you can use binary or hexadecimal format in python. For example: 0b0010 and 0x1a") + + def postprocessing(self): + # extract python codes; codes may be more than one + python_codes = self.extract_code(self.response, "python") + response = "" + for python_code in python_codes: + response += python_code + "\n" + self.response = response + +STAGE3B_TXT1="""1. background: Your task is to write a verilog testbench for an verilog RTL module code (we call it as "DUT", device under test). The infomation we have is the problem description that guides student to write the RTL code (DUT) and the header of the "DUT". Our target is to generate the verilog testbench for the DUT. This testbench can check if the DUT in verilog satisfies all technical requirements of the problem description. +2. Task: you are in section 3. in this section, please give me the golden RTL code that fullfill the description. This golden RTL code should have the same input and output ports as module header. The name of the module is "golden_DUT". The module will be the reference module in the final testbench. The final testbench will compare the golden RTL's output signals with DUT's output signals. If the same in all cases, the test passes. Your current task is to generate the golden RTL module. +3. Prior Knowledge: We already have the core rules expressed in python. You can use this infomation to help you design your golden RTL. You can use high level syntax and unsynthesizable syntax. Your golden module name is "golden_DUT" and ports are the same as DUT's ports. +4. your information is:""" +class Stage3b(BaseScriptStage): + def __init__(self, prob_data, response_stage1, response_stage3, **gptkwargs) -> None: + super().__init__("stage_3b", **gptkwargs) + self.prob_data = prob_data + self.response_stage1 = response_stage1 + self.response_stage3 = response_stage3 + self.txt1 = STAGE3B_TXT1 + + def make_prompt(self): + self.prompt = "" + self.add_prompt_line(self.txt1) + # problem description + self.add_prompt_line("RTL circuit problem description:") + self.add_prompt_line(self.prob_data["description"]) + # specification + self.add_prompt_line("RTL testbench specification:") + self.add_prompt_line(self.response_stage1) + # DUT header + self.add_prompt_line("DUT header:") + self.add_prompt_line(self.prob_data["header"]) + # rules + self.add_prompt_line("IMPORTANT: THE RULES OF IDEAL DUT:") + self.add_prompt_line(self.response_stage3) + # end + self.add_prompt_line("please generate the golden module code. please only generate the verilog codes, no other words.") + + def postprocessing(self): + # verilog codes + self.response = self.extract_code(self.response, "verilog")[-1] + +STAGE4_TXT1="""1. Your task is to write a verilog testbench for an verilog RTL module code (we call it as "DUT", device under test). The infomation we have is +- 1.1. the problem description that guides student to write the RTL code (DUT) and the header of the "DUT". +- 1.2. the module header. +- 1.3. the technical specification of testbench +- 1.4. test scenarios which determines value and sequential information of test vectors +- 1.5. the golden RTL codes in verilog. In testbench you should compare the signals from golden RTL and DUT. If not the same, then this DUT fails in the test. +Our target is to generate the verilog testbench for the DUT. This testbench can check if the DUT in verilog satisfies all technical requirements from the problem description. +2. you are in section 4. in this section, you will be provided with test scenarios and golden DUT. please highly based on these information to generate the testbench. +3. There should be a reg "error". It is "0" at the beginning. In each scenario, if test fails, the error should become "1" permanently and testbench should print like "scenario ... failed, got ..., expected ...". At the end of the test, if the "error" is still "0", testbench should print "All test cases passed!". This is very important! +4. In the scenarios testing part, do not directly write the value of expected value, but generate expected value from golden RTL. +5. your information is:""" +class Stage4(BaseScriptStage): + def __init__(self, prob_data, response_stage1, response_stage2, response_stage3b, **gptkwargs) -> None: + super().__init__("stage_4", **gptkwargs) + self.prob_data = prob_data + self.response_stage1 = response_stage1 + self.response_stage2 = response_stage2 + self.response_stage3b = response_stage3b + self.txt1 = STAGE4_TXT1 + self.TB_code_out = "" + + def make_prompt(self): + self.prompt = "" + self.add_prompt_line(self.txt1) + # problem description + self.add_prompt_line("RTL circuit problem description:") + self.add_prompt_line(self.prob_data["description"]) + # specification + self.add_prompt_line("RTL testbench specification:") + self.add_prompt_line(self.response_stage1) + # DUT header + self.add_prompt_line("DUT header:") + self.add_prompt_line(self.prob_data["header"]) + # rules + self.add_prompt_line("IMPORTANT - test scenario:") + self.add_prompt_line(self.response_stage2) + # rules + self.add_prompt_line("IMPORTANT - golden RTL: (please instantiate it in your testbench. Your code should not contain the full code of golden RTL)") + self.add_prompt_line(self.response_stage3b) + # end + self.add_prompt_line("please generate the golden module code. please only generate the verilog codes, no other words.") + + def postprocessing(self): + # verilog codes + self.response = self.extract_code(self.response, "verilog")[-1] + self.TB_code_out = self.response + "\n" + self.response_stage3b + + +class StageChecklist(BaseScriptStage): + def __init__(self, TB_code:str, checklist_str:str, max_iter:int, **gptkwargs) -> None: + super().__init__("stage_checklist", **gptkwargs) + self.checklist = checklist_str + self.max_iter = max_iter + self.TB_code_out = TB_code + self.exit = False + self.iter = 0 + self.TB_modified = False + + def make_prompt(self): + self.prompt = "" + self.add_prompt_line("please check the if the testbench code contains all the items in the checklist:") + self.add_prompt_line("testbench code here...\n") + self.add_prompt_line(self.TB_code_out + "\n") + self.add_prompt_line("please check the if the testbench code above contains all the scenarios in the checklist:") + self.add_prompt_line(self.checklist) + self.add_prompt_line("please reply 'YES' if all the items are included. If some of the items are missed in testbench, please add the missing items and reply the modified testbench code (full code).") + self.add_prompt_line("VERY IMPORTANT: please ONLY reply 'YES' or the full code modified. NEVER remove other irrelevant codes!!!") + + def postprocessing(self): + self.iter += 1 + if "YES" in self.response: + self.exit = True + else: + self.TB_modified = True + self.TB_code_out = self.extract_code(self.response, "verilog")[-1] + + def run(self): + self.TB_modified = False + while (not self.exit) and (self.iter < self.max_iter): + self.make_prompt() + self.call_gpt() + self.postprocessing() + + +######################################################################## +def test(): + test_stage1 = Stage1(model = "gpt-3.5-turbo", gptkeypath = "gpt_key/gpt_key_0306.json") + test_stage1.make_prompt() + print(test_stage1.prompt) + diff --git a/prompt_scripts/public_stages.py b/prompt_scripts/public_stages.py new file mode 100644 index 0000000..17475db --- /dev/null +++ b/prompt_scripts/public_stages.py @@ -0,0 +1,10 @@ +""" +Description : the public stages that may be used by other scripts +Author : Ruidi Qiu (r.qiu@tum.de) +Time : 2024/3/30 23:58:28 +LastEdited : 2024/3/31 00:00:30 +""" + +from .base_script import BaseScriptStage + +# not implemented yet \ No newline at end of file diff --git a/prompt_scripts/script_directgen.py b/prompt_scripts/script_directgen.py new file mode 100644 index 0000000..c69d0c6 --- /dev/null +++ b/prompt_scripts/script_directgen.py @@ -0,0 +1,60 @@ +""" +Description : "directgen" script for prompt scripts +Author : Ruidi Qiu (r.qiu@tum.de) +Time : 2024/3/30 17:40:38 +LastEdited : 2024/5/1 17:44:05 +""" + +from .base_script import BaseScript, BaseScriptStage, TESTBENCH_TEMPLATE + +class WF_directgen(BaseScript): + """ + stages: stage1 + """ + def __init__(self, prob_data:dict, task_dir:str, config:object): + super().__init__(prob_data, task_dir, config) + + def make_and_run_stages(self): + # stage1 + stage1 = Stage1(self.prob_data, **self.gptkwargs) + self.stage_operation(stage1) + + def make_and_run_reboot_stages(self, debug_dir): + # stage1 + stage1 = Stage1(self.prob_data, **self.gptkwargs) + self.stage_operation(stage1, debug_dir, reboot_en=True) + +STAGE1_TXT1 = """ +Your task is to write a verilog testbench for an verilog RTL module code (we call it as "DUT", device under test). The infomation we have is the problem description that guides student to write the RTL code (DUT) and the header of the "DUT". +""" +STAGE1_TXT2 = """ +very very IMPORTANT: If all the test cases pass, the testbench should display "all test cases passed". If any one of the test cases fails, testbench should not display "all test caess passed". DO NOT generate any .vcd file. +please don't reply other words except the testbench codes. +""" +class Stage1(BaseScriptStage): + def __init__(self, prob_data, **gptkwargs) -> None: + super().__init__("stage_1", **gptkwargs) + self.prob_data = prob_data + self.txt1 = STAGE1_TXT1 + self.txt2 = STAGE1_TXT2 + self.TB_code_out = "" + + def make_prompt(self): + self.prompt = "" + self.add_prompt_line(self.txt1) + # testbench template + self.add_prompt_line("your testbench template is:") + self.add_prompt_line(TESTBENCH_TEMPLATE) + # problem description + self.add_prompt_line("problem description:") + self.add_prompt_line(self.prob_data["description"]) + # DUT header + self.add_prompt_line("DUT header:") + self.add_prompt_line(self.prob_data["header"]) + # end + self.add_prompt_line(self.txt2) + + def postprocessing(self): + # verilog codes + self.response = self.extract_code(self.response, "verilog")[-1] + self.TB_code_out = self.response \ No newline at end of file diff --git a/prompt_scripts/script_pychecker.py b/prompt_scripts/script_pychecker.py new file mode 100644 index 0000000..73d9e1c --- /dev/null +++ b/prompt_scripts/script_pychecker.py @@ -0,0 +1,311 @@ +""" +Description : The prompt script for pychecker workflow +Author : Ruidi Qiu (r.qiu@tum.de) +Time : 2024/3/22 10:40:43 +LastEdited : 2024/8/25 00:05:24 +""" + +import json +from . import utils +from .base_script import BaseScript, BaseScriptStage +from .script_pychecker_CMB_new import Stage4 as Stage4_CMB, Stage5 as Stage5_CMB +from .script_pychecker_SEQ import Stage4_SEQ, Stage4b_SEQ, Stage5_SEQ + +class WF_pychecker(BaseScript): + """ + stages: stage1, stage2, stage3, stage3b, stage4 + check: check "scenario list"(stage2) in stage 4 + """ + def __init__(self, prob_data:dict, task_dir:str, config:object): + super().__init__(prob_data, task_dir, config) + self.max_check_iter = self.config.autoline.checklist.max + self.py_debug_focus = True + + def make_and_run_stages(self): + # stage0 + self.stage0 = Stage0(self.prob_data, **self.gptkwargs) + self.stage_operation(self.stage0) + self.circuit_type = self.stage0.circuit_type + # stage1 + self.stage1 = Stage1(self.prob_data, **self.gptkwargs) + self.stage_operation(self.stage1) + # stage2 + self.stage2 = Stage2(self.prob_data, self.stage1.response, **self.gptkwargs) + self.stage_operation(self.stage2) + self.scenario_num = self.stage2.scenario_num + self.scenario_dict = self.stage2.scenario_dict + # stage3 + self.stage3 = Stage3(self.prob_data, self.stage1.response, self.stage2.response, **self.gptkwargs) + self.stage_operation(self.stage3) + # split into CMB and SEQ + if self.circuit_type == "CMB": + self.make_and_run_stages_CMB() + else: + self.make_and_run_stages_SEQ() + + def make_and_run_stages_CMB(self): + # stage4 + self.stage4 = Stage4_CMB(self.prob_data, self.stage1.response, self.stage2.response, **self.gptkwargs) + self.stage_operation(self.stage4) + # stagechecklist + self.stagecheck = StageChecklist(self.TB_code, self.stage2.response, self.max_check_iter, **self.gptkwargs) + self.stage_operation(self.stagecheck) + # we perform pychecker_CMB_TB_standardization after stagechecklist because there is no stage 4b + # self.TB_code = utils.pychecker_CMB_TB_standardization(self.TB_code, self.prob_data["header"]) + # stage5 + self.stage5 = Stage5_CMB(self.prob_data, self.stage1.response, self.stage3.response, **self.gptkwargs) + self.stage_operation(self.stage5) + + def make_and_run_stages_SEQ(self): + # stage4 + self.stage4 = Stage4_SEQ(self.prob_data, self.stage1.response, self.stage2.response, **self.gptkwargs) + self.stage_operation(self.stage4) + # stagechecklist + self.stagecheck = StageChecklist(self.TB_code, self.stage2.response, self.max_check_iter, **self.gptkwargs) + self.stage_operation(self.stagecheck) + # stage4b + self.stage4b = Stage4b_SEQ(self.prob_data, self.TB_code, **self.gptkwargs) + self.stage_operation(self.stage4b) + # stage5 + self.stage5 = Stage5_SEQ(self.prob_data, self.stage1.response, self.stage3.response, **self.gptkwargs) + self.stage_operation(self.stage5) + + def make_and_run_reboot_stages(self, debug_dir): + if self.circuit_type == "CMB": + self.make_and_run_reboot_stages_CMB(debug_dir) + else: + self.make_and_run_reboot_stages_SEQ(debug_dir) + + def make_and_run_reboot_stages_CMB(self, debug_dir): + if self.reboot_mode == "TB": + # stage4 + self.stage4 = Stage4_CMB(self.prob_data, self.stage1.response, self.stage2.response, **self.gptkwargs) + self.stage_operation(self.stage4, debug_dir, reboot_en=True) + # stagechecklist + self.stagecheck = StageChecklist(self.TB_code, self.stage2.response, self.max_check_iter, **self.gptkwargs) + self.stage_operation(self.stagecheck, debug_dir, reboot_en=True) + # pychecker_CMB_TB_standardization + # self.TB_code = utils.pychecker_CMB_TB_standardization(self.TB_code, self.prob_data["header"]) + elif self.reboot_mode == "PY": + # stage5 + self.stage5 = Stage5_CMB(self.prob_data, self.stage1.response, self.stage3.response, **self.gptkwargs) + self.stage_operation(self.stage5, debug_dir, reboot_en=True) + else: + raise ValueError("invalid reboot_mode in WF_pychecker script (circuit type: CMB)") + + def make_and_run_reboot_stages_SEQ(self, debug_dir): + if self.reboot_mode == "TB": + # stage4 + self.stage4 = Stage4_SEQ(self.prob_data, self.stage1.response, self.stage2.response, **self.gptkwargs) + self.stage_operation(self.stage4, debug_dir, reboot_en=True) + # stagechecklist + self.stagecheck = StageChecklist(self.TB_code, self.stage2.response, self.max_check_iter, **self.gptkwargs) + self.stage_operation(self.stagecheck, debug_dir, reboot_en=True) + # stage4b + self.stage4b = Stage4b_SEQ(self.prob_data, self.TB_code, **self.gptkwargs) + self.stage_operation(self.stage4b, debug_dir, reboot_en=True) + elif self.reboot_mode == "PY": + # stage5 + self.stage5 = Stage5_SEQ(self.prob_data, self.stage1.response, self.stage3.response, **self.gptkwargs) + self.stage_operation(self.stage5, debug_dir, reboot_en=True) + else: + raise ValueError("invalid reboot_mode in WF_pychecker script (circuit type: SEQ)") + + + +SIGNALTEMP_PLACEHOLDER_1 = "/* SIGNAL TEMPLATE 1 */" +SIGNALTEMP_PLACEHOLDER_1A = "/* SIGNAL TEMPLATE 1A */" +SIGNALTEMP_PLACEHOLDER_1B = "/* SIGNAL TEMPLATE 1B */" + + +class Stage0(BaseScriptStage): + def __init__(self, prob_data, **gptkwargs) -> None: + super().__init__("stage_0", **gptkwargs) + self.prob_data = prob_data + self.circuit_type = None + + def make_prompt(self): + self.add_prompt_line("Please generate the verilog RTL code according to the following description and header information:") + self.add_prompt_line("problem description:") + self.add_prompt_line(self.prob_data["description"]) + self.add_prompt_line("RTL header:") + self.add_prompt_line(self.prob_data["header"]) + self.add_prompt_line("please only reply verilog codes. reply_format:\n```verilog\nyour_code_here...\n```") + + def postprocessing(self): + self.response = self.extract_code(self.response, "verilog")[-1] + self.circuit_type = utils.circuit_type_by_code(self.response) + +STAGE1_TXT1="""1. Your task is to write a verilog testbench for an verilog RTL module code (we call it as "DUT", device under test). The infomation we have is the problem description that guides student to write the RTL code (DUT) and the header of the "DUT". Our target is to generate the verilog testbench for the DUT. This testbench can check if the DUT in verilog satisfies all technical requirements of the problem description. +2. You are in the first stage. In this stage, please summarize the technical details of the DUT and give me a technical specification of the testbench generation task, so we can use it to design its corresponding testbench. +3. The core of testbench is the testcases. It usually include two parts logically: the input signals to the DUT and the expected result signals from DUT. The testbench will send the input signals to DUT and check if the result signals are the same as the expected result signals. If they are the same, this means the DUT is passed. Otherwise the DUT fails. +4. Your technical specification should include these sections: +- section 1: specification of the DUT, including the module header of the RTL code. If table or other detailed data is provided in the original problem description, DO repeat them in your response. They are very important!!! +5. your response should be in the form of JSON. +6. below is the information including the problem description and the DUT header:""" +STAGE1_TXT2="""your response must be in JSON form. example: +{ + "important data": "...", # type: string. If no table, state transition or other direct data, leave this with "" + "technical specifications": ["...", "...", ...] # each element of the list is one specification string, the starting of the string is its index +} +""" +class Stage1(BaseScriptStage): + def __init__(self, prob_data, **gptkwargs): + gptkwargs["json_mode"] = True + super().__init__("stage_1", **gptkwargs) + self.prob_data = prob_data + self.txt1 = STAGE1_TXT1 + self.txt2 = STAGE1_TXT2 + + def make_prompt(self): + self.prompt = "" + self.add_prompt_line(self.txt1) + # problem description + self.add_prompt_line("RTL circuit problem description:") + self.add_prompt_line(self.prob_data["description"]) + # DUT header + self.add_prompt_line("DUT header:") + self.add_prompt_line(self.prob_data["header"]) + # template + self.add_prompt_line(self.txt2) + + # def postprocessing(self): + # self.spec_dict = json.loads(self.response) + + +STAGE2_TXT1="""1. Your task is to write a verilog testbench for an verilog RTL module code (we call it as "DUT", device under test). The infomation we have is the problem description that guides student to write the RTL code (DUT) and the header of the "DUT". Our target is to generate the verilog testbench for the DUT. This testbench can check if the DUT in verilog satisfies all technical requirements of the problem description. +2. you are in section 2. in this section, please give me the test scenarios. you only need to describe the stimulus in each test scenarios. If time is important, please inform the clock cycle information. we will use the stimulus description to generate the test vectors and send them to DUT. you must not tell the expected results even though you know that. +3. your information is:""" +STAGE2_TXT2=""" +you only need to describe the stimulus in each test scenarios. If time is important, please inform the clock cycle information. we will use the stimulus description to generate the test vectors and send them to DUT. you must not tell the expected results even though you know that. + +your response must be in JSON form. example: +{ + "scenario 1": "...", # each content is a string + "scenario 2": "...", + "scenario 3": "...", + ... +}""" +class Stage2(BaseScriptStage): + def __init__(self, prob_data, response_stage1, **gptkwargs) -> None: + gptkwargs["json_mode"] = True + super().__init__("stage_2", **gptkwargs) + self.prob_data = prob_data + self.response_stage1 = response_stage1 + self.txt1 = STAGE2_TXT1 + self.txt2 = STAGE2_TXT2 + + def make_prompt(self): + self.prompt = "" + self.add_prompt_line(self.txt1) + # problem description + self.add_prompt_line("RTL circuit problem description:") + self.add_prompt_line(self.prob_data["description"]) + # specification + self.add_prompt_line("RTL testbench specification:") + self.add_prompt_line(self.response_stage1) + # DUT header + self.add_prompt_line("DUT header:") + self.add_prompt_line(self.prob_data["header"]) + # template + self.add_prompt_line(self.txt2) + + def postprocessing(self): + if "```json" in self.response: + self.response = self.extract_code(self.response, "json")[-1] + try: + self.scenario_dict = json.loads(self.response) + self.scenario_num = len(self.scenario_dict.keys()) + except: + self.scenario_dict = None + self.scenario_num = None + +STAGE3_TXT1="""1. Your task is to write a verilog testbench for an verilog RTL module code (we call it as "DUT", device under test). The information we have is the problem description that guides student to write the RTL code (DUT) and the header of the "DUT". Our target is to generate the verilog testbench for the DUT. This testbench can check if the DUT in verilog satisfies all technical requirements of the problem description. +2. you are in stage 3; in this stage, please give me the core rules of an ideal DUT. you should give these rules in python. (For convenience, you can use binary or hexadecimal format in python, i.e. 0b0010 and 0x1a). Later we will use these ideal rules to generate expected values in each test scenario. currently you must only generate the core part of the rules. the input of these rules should be related to the test vectors from test scenario. the rule should give the expected values under test vectors. You don't need to consider the control signals like clk or reset, unless the core rules of this task are about these signals. You can use numpy, scipy or other third party python libraries to help you write the rules. Please import them if you need. +3. your information is:""" + +class Stage3(BaseScriptStage): + def __init__(self, prob_data, response_stage1, response_stage2, **gptkwargs) -> None: + super().__init__("stage_3", **gptkwargs) + self.prob_data = prob_data + self.response_stage1 = response_stage1 + self.response_stage2 = response_stage2 + self.txt1 = STAGE3_TXT1 + + def make_prompt(self): + self.prompt = "" + self.add_prompt_line(self.txt1) + # problem description + self.add_prompt_line("RTL circuit problem description:") + self.add_prompt_line(self.prob_data["description"]) + # specification + self.add_prompt_line("RTL testbench specification:") + self.add_prompt_line(self.response_stage1) + # DUT header + self.add_prompt_line("DUT header:") + self.add_prompt_line(self.prob_data["header"]) + # test scenarios + self.add_prompt_line("test scenario: (please note the test vectors below, it will help you determine the input parameters of the rules)") + self.add_prompt_line(self.response_stage2) + # end + self.add_prompt_line("your response should only contain python code. For convenience, you can use binary or hexadecimal format in python. For example: 0b0010 and 0x1a") + + def postprocessing(self): + # extract python codes; codes may be more than one + python_codes = self.extract_code(self.response, "python") + response = "" + for python_code in python_codes: + response += python_code + "\n" + self.response = response + +class StageChecklist(BaseScriptStage): + def __init__(self, TB_code:str, checklist_str:str, max_iter:int, **gptkwargs) -> None: + super().__init__("stage_checklist", **gptkwargs) + self.checklist = checklist_str # {"scenario 1": "xxx", "scenario 2": "xxx", ...} + self.checklist_dict = json.loads(checklist_str) + self.missing_scenarios = [] + self.max_iter = max_iter + self.TB_code_out = TB_code + self.exit = False + self.iter = 0 + self.TB_modified = False + + def make_prompt(self): + self.prompt = "" + self.add_prompt_line("please check the if the testbench code contains all the items in the checklist:") + self.add_prompt_line("testbench code here...\n") + self.add_prompt_line(self.TB_code_out + "\n") + self.add_prompt_line("please check the if the testbench code above contains all the scenarios in the checklist:") + self.add_prompt_line(self.checklist) + self.add_prompt_line("please reply 'YES' if all the items are included. If some of the items are missed in testbench, please add the missing items and reply the modified testbench code (full code).") + self.add_prompt_line("HINT: the missing scenarios may be: " + str(self.missing_scenarios)) + self.add_prompt_line("VERY IMPORTANT: please ONLY reply 'YES' or the full code modified. NEVER remove other irrelevant codes!!!") + + def postprocessing(self): + self.iter += 1 + if "YES" in self.response or "Yes" in self.response or "yes" in self.response: + self.exit = True + else: + self.TB_modified = True + self.TB_code_out = self.extract_code(self.response, "verilog")[-1] + + def pre_check(self): + """this function is called at the beginning of run() so that the stage can be skipped if needed""" + self.missing_scenarios = [] + for key in self.checklist_dict.keys(): + if key.replace(" ", " = ") not in self.TB_code_out: + self.missing_scenarios.append(key) + + def run(self): + self.TB_modified = False + while (not self.exit) and (self.iter < self.max_iter): + self.pre_check() + if self.missing_scenarios == []: + self.exit = True + self.conversation_message += "\n[SYSTEM PRECHECK] All scenarios are included in the testbench code. You can continue to the next stage." + else: + self.make_prompt() + self.call_gpt() + self.postprocessing() + +# more stages see script_pychecker_CMB and script_pychecker_SEQ \ No newline at end of file diff --git a/prompt_scripts/script_pychecker_CMB_new.py b/prompt_scripts/script_pychecker_CMB_new.py new file mode 100644 index 0000000..26b3194 --- /dev/null +++ b/prompt_scripts/script_pychecker_CMB_new.py @@ -0,0 +1,313 @@ +""" +Description : The prompt script for pychecker workflow +Author : Ruidi Qiu (r.qiu@tum.de) +Time : 2024/3/22 10:40:43 +LastEdited : 2024/8/25 11:55:28 +""" + +from .base_script import BaseScript, BaseScriptStage +from .legacy import script_RTLchecker0306 +from .legacy.script_RTLchecker0306 import StageChecklist +from . import utils + +class WF_pychecker_CMB(BaseScript): + """ + stages: stage1, stage2, stage3, stage3b, stage4 + check: check "scenario list"(stage2) in stage 4 + """ + def __init__(self, prob_data:dict, task_dir:str, config:object): + super().__init__(prob_data, task_dir, config) + self.max_check_iter = self.config.autoline.checklist.max + self.py_code = "" + + def make_and_run_stages(self): + # stage1 + self.stage1 = Stage1(self.prob_data, **self.gptkwargs) + self.stage_operation(self.stage1) + # stage2 + self.stage2 = Stage2(self.prob_data, self.stage1.response, **self.gptkwargs) + self.stage_operation(self.stage2) + # stage3 + self.stage3 = Stage3(self.prob_data, self.stage1.response, self.stage2.response, **self.gptkwargs) + self.stage_operation(self.stage3) + # stage4 + self.stage4 = Stage4(self.prob_data, self.stage1.response, self.stage2.response, **self.gptkwargs) + self.stage_operation(self.stage4) + # stagechecklist + self.stagecheck = StageChecklist(self.TB_code, self.stage2.response, self.max_check_iter, **self.gptkwargs) + self.stage_operation(self.stagecheck) + # stage5 + self.stage5 = Stage5(self.prob_data, self.stage1.response, self.stage3.response, **self.gptkwargs) + self.stage_operation(self.stage5) + # self.TB_code += "\n" + stage3b.response + + def make_and_run_reboot_stages(self, debug_dir): + if self.reboot_mode == "TB": + # stage4 + self.stage4 = Stage4(self.prob_data, self.stage1.response, self.stage2.response, **self.gptkwargs) + self.stage_operation(self.stage4, debug_dir, reboot_en=True) + # stagechecklist + self.stagecheck = StageChecklist(self.TB_code, self.stage2.response, self.max_check_iter, **self.gptkwargs) + self.stage_operation(self.stagecheck, debug_dir, reboot_en=True) + elif self.reboot_mode == "PY": + # stage5 + self.stage5 = Stage5(self.prob_data, self.stage1.response, self.stage3.response, **self.gptkwargs) + self.stage_operation(self.stage5, debug_dir, reboot_en=True) + else: + raise ValueError("invalid reboot_mode in WF_pychecker script (circuit type: CMB)") + +class Stage1(script_RTLchecker0306.Stage1): + """ + stage1 for pychecker, the same as RTLchecker0306.Stage1 + """ + def __init__(self, prob_data:dict, **gptkwargs): + super().__init__(prob_data, **gptkwargs) + +class Stage2(script_RTLchecker0306.Stage2): + """ + stage2 for pychecker, the same as RTLchecker0306.Stage2 + """ + def __init__(self, prob_data:dict, response_stage1:str, **gptkwargs): + super().__init__(prob_data, response_stage1, **gptkwargs) + +class Stage3(script_RTLchecker0306.Stage3): + """ + stage3 for pychecker, the same as RTLchecker0306.Stage3 + """ + def __init__(self, prob_data:dict, response_stage1:str, response_stage2:str, **gptkwargs): + super().__init__(prob_data, response_stage1, response_stage2, **gptkwargs) + +SIGNALTEMP_PLACEHOLDER_1 = "/* SIGNAL TEMPLATE 1 */" +SIGNALTEMP_PLACEHOLDER_1A = "/* SIGNAL TEMPLATE 1A */" +SIGNALTEMP_PLACEHOLDER_1B = "/* SIGNAL TEMPLATE 1B */" + +STAGE4_TXT1 = """ +1. Your task is to write a verilog testbench for an verilog RTL module code (we call it as "DUT", device under test). The infomation we have is +- 1.1. the problem description that guides student to write the RTL code (DUT) and the header of the "DUT". +- 1.2. the module header. +- 1.3. the technical specification of testbench +- 1.4. test scenarios which determines value and sequential information of test vectors + +2. you are in section 4. in this section, our target is to generate the verilog testbench for the DUT. This testbench can export the input and output signals of DUT at the important time points. The exported data will be send to a python script to check the correctness of DUT. +ATTENTION: The testbench does not need to check the DUT's output but only export the signals of DUT. +Instruction of saving signals to file: +(1) you should use $fopen and $fdisplay to export the important signals in testbench. the file name is "TBout.txt". +(2) When running testbench, for one time point, you should export 1 line. the example of the printed line is "%s". There could be multiple $fdisplay statements under one scenario, which means multiple test stimuli in one scenario. +(3) Attention: before $fdisplay, you should always have a delay statement to make sure the signals are stable. +(4) the signals you save is the input and output of DUT, you should determine the signals according to DUT's header: +"""%(SIGNALTEMP_PLACEHOLDER_1) + +STAGE4_TXT2 = """ +The testbench does not need to check the DUT's output but only export the signals of DUT. +Instruction of saving signals to file: +(1) you should use $fopen and $fdisplay to export the important signals in testbench. the file name is "TBout.txt". +(2) When running testbench, for one time point, you should export 1 line. the example of the printed line is "%s"; There could be multiple $fdisplay statements under one scenario, which means multiple test stimuli in one scenario. +(3) Attention: before $fdisplay, you should always have a delay statement (#10) to make sure the signals are stable. +(4) the signals you save is the input and output of DUT, you should determine the signals according to DUT's header. +please only generate the verilog codes, no other words. +"""%(SIGNALTEMP_PLACEHOLDER_1) + +class Stage4(BaseScriptStage): + """stage 4: generate the testbench that export the signals of DUT to a file""" + def __init__(self, prob_data, response_stage1, response_stage2, **gptkwargs) -> None: + super().__init__("stage_4", **gptkwargs) + self.prob_data = prob_data + self.response_stage1 = response_stage1 + self.response_stage2 = response_stage2 + self.txt1 = STAGE4_TXT1 + self.txt2 = STAGE4_TXT2 + self.txt1 = self.txt1.replace(SIGNALTEMP_PLACEHOLDER_1, header_to_SignalTxt_template(prob_data["header"])) + self.txt2 = self.txt2.replace(SIGNALTEMP_PLACEHOLDER_1, header_to_SignalTxt_template(prob_data["header"])) + + def make_prompt(self): + self.prompt = "" + self.add_prompt_line(self.txt1) + # DUT header + self.add_prompt_line(self.prob_data["header"]) + # other information: + self.add_prompt_line("Your other information:") + # problem description + self.add_prompt_line("RTL circuit problem description:") + self.add_prompt_line(self.prob_data["description"]) + # specification + self.add_prompt_line("RTL testbench specification:") + self.add_prompt_line(self.response_stage1) + # rules + self.add_prompt_line("IMPORTANT - test scenario:") + self.add_prompt_line(self.response_stage2) + # end + self.add_prompt_line(self.txt2) + + def postprocessing(self): + # verilog codes + self.response = self.extract_code(self.response, "verilog")[-1] + self.TB_code_out = self.response + # newly added + self.TB_code_out = utils.pychecker_CMB_TB_standardization(self.TB_code_out, self.prob_data["header"]) + +STAGEPYGEN_PYFORMAT = """Your current task is: write a python class "GoldenDUT". This python class can represent the golden DUT (the ideal one). In your "GoldenDUT", you should do the following things: +- a. Write a method "def __init__(self)". Set the inner states/values of the golden DUT. The "__init__" method has no input parameters except "self". +- b. Write a method "def load(self, signal_vector)". This method is to load the important input signals and get the expected output signals. it should return the expected output values. It can call other methods to help computing the expected output. It will be called by other inner methods later. +- c. Write a method "def check(self, signal_vector)". This method is to call "load" to get the expected output values, and compare them with output signals from DUT. It should return True or False only. It can call other methods to help checking. +- d. write other methods you need, they can be called by "__init__", "load" or "check". +- e. the input of "load" and "check" is the signal vector. The signal vector is a dictionary, the key is the signal name, the value is the signal value. +You can use binary (like 0x1101), hexadecimal (like 0x1a) or normal number format in python. But the signal vector input to GoldenDUT is always in decimal format""" # TODO: later this function will also show the failed scenario idx + +# STAGEPYGEN_TXT1 = """ +# 1. background: Your task is to verify the functional correctness of a verilog RTL module code (we call it as "DUT", device under test). Our plan is to first export the signals (input and output) of the DUT under test scenarios. Then, we will use a python script to check the correctness of DUT. +# 2. You are in the last stage. In this stage, we already export the signals of DUT. Your task is to write a python script. The python script contains one main function "check_dut" and other functions to be called by "check_dut" (this is optional). The input of "check_dut" is the signals of DUT in the format below: (the signal names are real, but the values are just for example) +# %s +# The main function "check_dut" should check the correctness according to the input signals. The input signals are all in decimal format. It will be called by other codes later. +# 3. %s +# 4. You have the information below to help you check the correctness of DUT: +# """%(SIGNALTEMP_PLACEHOLDER_1, STAGEPYGEN_PYFORMAT) + +STAGEPYGEN_TXT1 = """ +1. background: Your task is to verify the functional correctness of a verilog RTL module code (we call it as "DUT", device under test). Our plan is to first export the signals (input and output) of the DUT under test scenarios. Then, we will use a python script to check the correctness of DUT. +2. You are in the last stage. In this stage, we already export the signals of DUT. The signals of DUT are in the format below: (the signal names are real, but the values are just for example) +%s +The input signals are all in decimal format. The "scenario" is not DUT's signal but to tell you the current scenario index. +3. %s +4. You have the information below to help you check the correctness of DUT: +"""%(SIGNALTEMP_PLACEHOLDER_1, STAGEPYGEN_PYFORMAT) + +STAGEPYGEN_TXT2 = """ +[IMPORTANT] %s +Optional: You can also use functions from numpy and scipy to help you check the correctness of DUT. +you can use binary (like 0b1011), hexadeciaml (like 0x1a) or normal number format in python for convenience. +please only generate the python codes, no other words. +"""%(STAGEPYGEN_PYFORMAT) + +STAGEPYGEN_TAIL1 = """ +def check_dut(vectors_in): + golden_dut = GoldenDUT() + failed_scenarios = [] + for vector in vectors_in: + check_pass = golden_dut.check(vector) + if check_pass: + print(f"Passed; vector: {vector}") + else: + print(f"Failed; vector: {vector}") + failed_scenarios.append(vector["scenario"]) + return failed_scenarios +""" + +STAGEPYGEN_TAIL2 = """ +def SignalTxt_to_dictlist(txt:str): + lines = txt.strip().split("\\n") + signals = [] + for line in lines: + signal = {} + line = line.strip().split(", ") + for item in line: + if "scenario" in item: + item = item.split(": ") + signal["scenario"] = item[1] + else: + item = item.split(" = ") + key = item[0] + value = item[1] + if "x" not in value and "z" not in value: + signal[key] = int(value) + else: + signal[key] = value + signals.append(signal) + return signals +with open("TBout.txt", "r") as f: + txt = f.read() +vectors_in = SignalTxt_to_dictlist(txt) +tb_pass = check_dut(vectors_in) +print(tb_pass) +""" +class Stage5(BaseScriptStage): + """stage 5: generate the pychecker that receive the signals from testbench and check the correctness of DUT""" + def __init__(self, prob_data, response_stage1, response_stage3, **gptkwargs) -> None: + super().__init__("stage_5", **gptkwargs) + self.prob_data = prob_data + self.response_stage1 = response_stage1 + self.response_stage3 = response_stage3 # currently not used + self.txt1 = STAGEPYGEN_TXT1.replace(SIGNALTEMP_PLACEHOLDER_1, utils.signal_dictlist_template(prob_data["header"], use_check_en=False)) + self.txt2 = STAGEPYGEN_TXT2 + self.pycode_tail = STAGEPYGEN_TAIL1 + STAGEPYGEN_TAIL2 + + def make_prompt(self): + self.prompt = "" + # introduction + self.add_prompt_line(self.txt1) + # problem description + self.add_prompt_line("RTL circuit problem description:") + self.add_prompt_line(self.prob_data["description"]) + # specification + self.add_prompt_line("Checker specification:") + self.add_prompt_line(self.response_stage1) + # python rules (optional) + self.add_prompt_line("Here is the basic rules in python for the module. It is generated in previous stage. You can use it as a reference, but you should write your own python script. This is just for your better understanding:") + self.add_prompt_line(self.response_stage3) + # end + self.add_prompt_line(self.txt2) + + def postprocessing(self): + # python codes + self.response = self.extract_code(self.response, "python")[-1] + self.Pychecker_code_out = self.response + self.pycode_tail + + # @staticmethod + # def signal_dictlist_template(header:str) -> str: + # """ + # for the automatic generation of signals in testbench + # target: given the DUT header, generate the signal output template + # eg: if we have a DUT header like "module DUT(input a, b, c, output d, e);", the signal output template should be like "[{"scenario": "1", "a": 1, "b": 0, "c":1, "d": 0, "e": 0}, {"scenario": "2", "a": 0, "b": 0, "c":1, "d": 0, "e": 0}]" + # """ + # signals1 = header_to_SignalTxt_template(header, "1") + # signals2 = header_to_SignalTxt_template(header, "2") + # signals_dictlist1 = SignalTxt_to_dictlist(signals1) + # signals_dictlist2 = SignalTxt_to_dictlist(signals2) + # signals_dictlist = signals_dictlist1 + signals_dictlist2 + # return str(signals_dictlist) + +def header_to_SignalTxt_template(header:str): + """ + - header: the header of DUT + - from header to signals in txt + - for the automatic generation of signals in testbench + - target: given the DUT header, generate the signal output template + - eg: if we have a DUT header like "module DUT(input clk, load, data, output q);", the signal output template should be like "$fdisplay(file, "scenario: %d, clk = %d, load = %d, data = %d, q = %d", scenario, clk, load, data, q);" + """ + signals = utils.extract_signals(header) + # generate ", clk = %d, load = %d, data = %d, q = %d" + signal_form1 = "" + signal_form2 = "" + for signal in signals: + signal_form1 += f", {signal['name']} = %d" + signal_form2 += f", {signal['name']}" + txt = r'$fdisplay(file, "scenario: %d' + signal_form1 + r'", scenario' + signal_form2 + r');' + return txt + +# def SignalTxt_to_dictlist(txt:str) -> list: +# """ +# - from txt to list of dicts +# - this function is used to extract signals and scenario information from a out.txt file. +# - the TBout.txt file is generated by testbench, which is in the pychecker workflow +# - the format of each line in TBout.txt is like: +# - "scenario: x, a = x, b = x, c = x, d = x, e = x" +# - we want: [{"scenario": x, "a": x, ...}, {...}] +# """ +# lines = txt.strip().split("\n") +# signals = [] +# for line in lines: +# signal = {} +# line = line.strip().split(", ") +# for item in line: +# if "scenario" in item: +# item = item.split(": ") +# signal["scenario"] = item[1] +# else: +# item = item.split(" = ") +# key = item[0] +# value = item[1] +# if "x" not in value and "z" not in value: +# signal[key] = int(value) +# else: +# signal[key] = value +# signals.append(signal) +# return signals \ No newline at end of file diff --git a/prompt_scripts/script_pychecker_SEQ.py b/prompt_scripts/script_pychecker_SEQ.py new file mode 100644 index 0000000..43df4d7 --- /dev/null +++ b/prompt_scripts/script_pychecker_SEQ.py @@ -0,0 +1,527 @@ +""" +Description : The prompt script for pychecker workflow +Author : Ruidi Qiu (r.qiu@tum.de) +Time : 2024/3/22 10:40:43 +LastEdited : 2024/9/3 17:01:56 +""" + +from .base_script import BaseScript, BaseScriptStage +from .legacy import script_RTLchecker0306 +from . import utils +from .utils import given_TB +# from .script_RTLchecker0306 import StageChecklist +import json + +class WF_pychecker_SEQ(BaseScript): + """ + WF_pychecker_SEQ + """ + def __init__(self, prob_data:dict, task_dir:str, config:object): + super().__init__(prob_data, task_dir, config) + self.max_check_iter = self.config.autoline.checklist.max + self.py_code = "" + self.py_debug_focus = True # only for SEQ + + def make_and_run_stages(self): + # stage0 + self.stage0 = Stage0(self.prob_data, **self.gptkwargs) + self.stage_operation(self.stage0) + # stage1 + self.stage1 = script_RTLchecker0306.Stage1(self.prob_data, **self.gptkwargs) + self.stage_operation(self.stage1) + # stage2 + self.stage2 = script_RTLchecker0306.Stage2(self.prob_data, self.stage1.response, **self.gptkwargs) + self.stage_operation(self.stage2) + # stage3 + self.stage3 = Stage3(self.prob_data, self.stage1.response, self.stage2.response, **self.gptkwargs) + self.stage_operation(self.stage3) + # stage4 + self.stage4 = Stage4_SEQ(self.prob_data, self.stage1.response, self.stage2.response, **self.gptkwargs) + self.stage_operation(self.stage4) + # stagechecklist + self.stagecheck = StageChecklist(self.TB_code, self.stage2.response, self.max_check_iter, **self.gptkwargs) + self.stage_operation(self.stagecheck) + # stage4b + self.stage4b = Stage4b_SEQ(self.prob_data, self.TB_code, **self.gptkwargs) + self.stage_operation(self.stage4b) + # stage5 + self.stage5 = Stage5_SEQ(self.prob_data, self.stage1.response, self.stage3.response, **self.gptkwargs) + self.stage_operation(self.stage5) + + + def make_and_run_reboot_stages(self, debug_dir): + if self.reboot_mode == "TB": + # stage4 + self.stage4 = Stage4_SEQ(self.prob_data, self.stage1.response, self.stage2.response, **self.gptkwargs) + self.stage_operation(self.stage4, debug_dir, reboot_en=True) + # stagechecklist + self.stagecheck = StageChecklist(self.TB_code, self.stage2.response, self.max_check_iter, **self.gptkwargs) + self.stage_operation(self.stagecheck, debug_dir, reboot_en=True) + # stage4b + self.stage4b = Stage4b_SEQ(self.prob_data, self.TB_code, **self.gptkwargs) + self.stage_operation(self.stage4b, debug_dir, reboot_en=True) + elif self.reboot_mode == "PY": + # stage5 + self.stage5 = Stage5_SEQ(self.prob_data, self.stage1.response, self.stage3.response, **self.gptkwargs) + self.stage_operation(self.stage5, debug_dir, reboot_en=True) + else: + raise ValueError("invalid reboot_mode in WF_pychecker script (circuit type: SEQ)") + +class Stage0(BaseScriptStage): + def __init__(self, prob_data, **gptkwargs) -> None: + super().__init__("stage_0", **gptkwargs) + self.prob_data = prob_data + self.circuit_type = None + + def make_prompt(self): + self.add_prompt_line("Please generate the verilog RTL code according to the following description and header information:") + self.add_prompt_line("problem description:") + self.add_prompt_line(self.prob_data["description"]) + self.add_prompt_line("RTL header:") + self.add_prompt_line(self.prob_data["header"]) + self.add_prompt_line("please only reply verilog codes, no other words.") + + def postprocessing(self): + self.response = self.extract_code(self.response, "verilog")[-1] + self.circuit_type = utils.circuit_type_by_code(self.response) + +SIGNALTEMP_PLACEHOLDER_1 = "/* SIGNAL TEMPLATE 1 */" +SIGNALTEMP_PLACEHOLDER_1A = "/* SIGNAL TEMPLATE 1A */" +SIGNALTEMP_PLACEHOLDER_1B = "/* SIGNAL TEMPLATE 1B */" + +STAGE3_TXT1="""1. Your task is to write a verilog testbench for an verilog RTL module code (we call it as "DUT", device under test). The information we have is the problem description that guides student to write the RTL code (DUT) and the header of the "DUT". Our target is to generate the verilog testbench for the DUT. This testbench can check if the DUT in verilog satisfies all technical requirements of the problem description. +2. you are in stage 3; in this stage, please give me the core rules of an ideal DUT. you should give these rules in python. (For convenience, you can use binary or hexadecimal format in python, i.e. 0b0010 and 0x1a). Later we will use these ideal rules to generate expected values in each test scenario. currently you must only generate the core part of the rules. the input of these rules should be related to the test vectors from test scenario. the rule should give the expected values under test vectors. You don't need to consider the control signals like clk or reset, unless the core rules of this task are about these signals. You can use numpy, scipy or other third party python libraries to help you write the rules. Please import them if you need. +3. your information is:""" + +class Stage3(BaseScriptStage): + def __init__(self, prob_data, response_stage1, response_stage2, **gptkwargs) -> None: + super().__init__("stage_3", **gptkwargs) + self.prob_data = prob_data + self.response_stage1 = response_stage1 + self.response_stage2 = response_stage2 + self.txt1 = STAGE3_TXT1 + + def make_prompt(self): + self.prompt = "" + self.add_prompt_line(self.txt1) + # problem description + self.add_prompt_line("RTL circuit problem description:") + self.add_prompt_line(self.prob_data["description"]) + # specification + self.add_prompt_line("RTL testbench specification:") + self.add_prompt_line(self.response_stage1) + # DUT header + self.add_prompt_line("DUT header:") + self.add_prompt_line(self.prob_data["header"]) + # test scenarios + self.add_prompt_line("test scenario: (please note the test vectors below, it will help you determine the input parameters of the rules)") + self.add_prompt_line(self.response_stage2) + # end + self.add_prompt_line("your response should only contain python code. For convenience, you can use binary or hexadecimal format in python. For example: 0b0010 and 0x1a") + + def postprocessing(self): + # extract python codes; codes may be more than one + python_codes = self.extract_code(self.response, "python") + response = "" + for python_code in python_codes: + response += python_code + "\n" + self.response = response + +def header_to_SignalTxt_template(header:str, template_scenario_idx:str="1", signal_value:str="0"): + """ + - header: the header of DUT + - template_scenario_idx: the scenario index in the template + - signal_value: the value of the signal in the template + - only: None: both input signal and output signal; "input": only input signal; "output": only output signal + - from header to signals in txt + - for the automatic generation of signals in testbench + - target: given the DUT header, generate the signal output template + - eg: if we have a DUT header like "module DUT(input a, b, c, output d, e);", the signal output template should be like "scenario: 1, a = 1, b = 0, c = 1, d = 0, e = 0" + """ + signals = header.split("(")[1].split(")")[0].split(",") + # remove the "input" and "output" keywords + signals = [signal.strip().split(" ")[-1] for signal in signals] + # generate the signal output template + signal_out = "scenario: " + template_scenario_idx + for signal in signals: + signal_out += f", {signal} = {signal_value}" + return signal_out + +STAGE4_SEQ_TXT1 = """ +1. Your task is to complete a given verilog testbench code. This testbench is for a verilog RTL module code (we call it as "DUT", device under test). This circuit is a sequential circuit. The infomation we have is +- 1.1. the problem description that guides student to write the RTL code (DUT) and the header of the "DUT". +- 1.2. the module header. +- 1.3. test scenarios which determines values and sequential information of test vectors +- 1.4. the testbench structure +- 1.5. the instruction of writing our testbench +""" + +# STAGE4_SEQ_INSTR = """ +# The design Instruction is: +# you should display the signals every clock cycle (#10). When it is time to check the output value of DUT, add [check] at the beginning of the output line +# There is a example code (partial) for a DFF circuit: +# exmaple (1): +# ``` +# // the input of DFF is "d", the output of DFF is "q", the clock signal is "clk" +# // scenario 1: test the function of DUT: +# scenario = 1; +# d = 1; $fdisplay(file, "scenario: 1, clk = %%d, d = %%d, q = %%d", clk, d, q); // set the input signal, display +# #10; +# $fdisplay(file, "[check]scenario: 1, clk = %%d, d = %%d, q = %%d", clk, d, q); // check the output signal, display +# #10; +# // scenario 2 +# scenario = 2; +# d = 0; $fdisplay(file, "scenario: 2, clk = %%d, d = %%d, q = %%d", clk, d, q); +# #10; +# $fdisplay(file, "[check]scenario: 2, clk = %%d, d = %%d, q = %%d", clk, d, q); +# #10; +# ... +# ``` +# example (2): +# for a scenario that needs multiple clock cycles before checking, the example code is like this: +# ``` +# // scenario 3: multiple clock cycles before checking +# scenario = 3 +# d = 1; $fdisplay(file, "scenario: 3, clk = %%d, d = %%d, q = %%d", clk, d, q); +# #10; +# repeat(5) begin +# $fdisplay(file, "scenario: 3, clk = %%d, d = %%d, q = %%d", clk, d, q); +# #10; +# end +# $fdisplay(file, "[check]scenario: 3, clk = %%d, d = %%d, q = %%d", clk, d, q); +# #10; +# ``` +# for a scenario that has many checking time points, the example code is like this: +# ``` +# // scenario 4: multi checking points +# scenario = 4; +# d = 1; $fdisplay(file, "scenario: 4, clk = %%d, d = %%d, q = %%d", clk, d, q); +# #10; +# repeat(5) begin +# $fdisplay(file, "[check]scenario: 4, clk = %%d, d = %%d, q = %%d", clk, d, q); +# #10; +# end +# ``` +# (3) the signals you save is the input and output of DUT, you should determine the signals according to DUT's header +# """ # not used currently + + +# STAGE4_SEQ_TXT2 = r""" +# The testbench does not need to check the DUT's output but only export the signals of DUT. Please determine the input signal's exact values according to given test scenarios. please only complement the last initial code part. your code should begin from the "initial begin..." part to "end". You must use %d when exporting values. +# """ +STAGE4_SEQ_TXT2 = """ +The testbench does not need to check the DUT's output but only export the signals of DUT. Please export the signals of DUT to a file named "TBout.txt" at the end of each scenario. The template is given below: +%s +The variables are already declared. The clock signal is already prepared. This output will be used to check the correctness of the DUT's output later. +please only use "#10" as the delay when you need. If you need longer delay, you can use multiple "#10", such as "#10; #10; #10;". Avoid meaningless long delay in your code. +If you need a loop in a scenario to check multiple time points, use "repeat" loop. for exmaple: +``` +// scenario x +scenario = x; +signal_1 = 1; +repeat(5) begin + %s + #10; +end +``` +Please determine the input signal's exact values according to given test scenarios. +Note: please complete the last initial code part (marked in the given testbench template). You should give me the completed full code. The testbench template above is to help you generate the code. You must use %%d when exporting values. +please generate the full testbench code. please only reply verilog codes, no other words. +"""%(SIGNALTEMP_PLACEHOLDER_1, SIGNALTEMP_PLACEHOLDER_1) +class Stage4_SEQ(BaseScriptStage): + """stage 4 (SEQ): generate the testbench that export the signals of DUT to a file""" + def __init__(self, prob_data, response_stage1, response_stage2, **gptkwargs) -> None: + super().__init__("stage_4", **gptkwargs) + self.prob_data = prob_data + self.response_stage1 = response_stage1 + self.response_stage2 = response_stage2 + signals_output_template = self.header_to_SignalTxt_template(prob_data["header"], check_en=True) + # self.txt_instruction = STAGE4_SEQ_INSTR.replace(SIGNALTEMP_PLACEHOLDER_1, signals_input_template).replace(SIGNALTEMP_PLACEHOLDER_1A, signals_output_template) + self.txt1 = STAGE4_SEQ_TXT1 + self.txt2 = STAGE4_SEQ_TXT2.replace(SIGNALTEMP_PLACEHOLDER_1, signals_output_template) + self.TB_code_object = given_TB(prob_data["header"]) + # signal_template_scenario = signals_input_template + "\n" + signals_input_template + "\n" + signals_input_template + "\n" + signals_output_template + + def make_prompt(self): + self.prompt = "" + self.add_prompt_line(self.txt1) + # DUT header + self.add_prompt_line("DUT header:") + self.add_prompt_line(self.prob_data["header"]) + # other information: + self.add_prompt_line("Your other information:") + # problem description + self.add_prompt_line("RTL circuit problem description:") + self.add_prompt_line(self.prob_data["description"]) + # scenarios + self.add_prompt_line("IMPORTANT - test scenario (Please determine the values of input signals according to these test scenarios.):") + self.add_prompt_line(self.response_stage2) + # given codes + self.add_prompt_line("below is the given testbench codes:") + self.add_prompt_line(self.TB_code_object.gen_template()) + # end + self.add_prompt_line(self.txt2) + + def postprocessing(self): + # verilog codes + self.response = self.extract_code(self.response, "verilog")[-1] + # self.TB_code_object.TB_code_test = self.response + # self.TB_code_out = self.TB_code_object.gen_template() + self.TB_code_out = self.response + self.TB_code_out = utils.verilog_patch(self.TB_code_out) + + @staticmethod + def header_to_SignalTxt_template(header:str, check_en = False): + """ + - header: the header of DUT + - only: None: both input signal and output signal; "input": only input signal; "output": only output signal + - from header to signals in txt + - for the automatic generation of signals in testbench + - target: given the DUT header, generate the signal output template + - eg: if we have a DUT header like "module DUT(input clk, load, data, output q);", the signal output template should be like "$fdisplay(file, "scenario: %d, clk = %d, load = %d, data = %d, q = %d", scenario, clk, load, data, q);" + """ + signals = utils.extract_signals(header) + # generate ", clk = %d, load = %d, data = %d, q = %d" + signal_form1 = "" + signal_form2 = "" + for signal in signals: + signal_form1 += f", {signal['name']} = %d" + signal_form2 += f", {signal['name']}" + if check_en: + txt = r'$fdisplay(file, "[check]scenario: %d' + signal_form1 + r'", scenario' + signal_form2 + r');' + else: + txt = r'$fdisplay(file, "scenario: %d' + signal_form1 + r'", scenario' + signal_form2 + r');' + return txt + + + +Stage4b_SEQ_TXT1 = """given the scenario based verilog testbench code below:""" +Stage4b_SEQ_TXT2 = """ +please help me to export the input of DUT module by using code below: + +[IMPORTANT]: +%s + +you should insert the code above into scenario checking part. In each scenario, you should insert the code above after the input of DUT module changed. Don't delete the existing $display codes. + +For example, for a circuit that has two input signals changed at different times in one scenario, the original code is like this: +- original code: +// scenario 1 begins +scenario = 1; +signal_1 = 1; +// insert $fdisplay here +#10; +signal_2 = 1; +// insert $fdisplay here +#10; +$fdisplay(file, "[check]scenario: %%d, signal_1 = %%d, signal_2 = %%d", scenario, signal_1, signal_2); // this should be reserved. Never change the existing codes. +#10; +// scenario 1 ends + +- after insertion: +// scenario 1 begins +scenario = 1; +signal_1 = 1; +$fdisplay(file, "scenario: %%d, signal_1 = %%d, signal_2 = %%d", scenario, signal_1, signal_2); +#10; +signal_2 = 1; +$fdisplay(file, "scenario: %%d, signal_1 = %%d, signal_2 = %%d", scenario, signal_1, signal_2); +#10; +$fdisplay(file, "[check]scenario: %%d, signal_1 = %%d, signal_2 = %%d", scenario, signal_1, signal_2); +#10; +// scenario 1 ends + +please insert codes according to the rules above. DO NOT modify other codes! please reply the modified full codes. please only reply verilog codes, no other words."""%(SIGNALTEMP_PLACEHOLDER_1) +class Stage4b_SEQ(BaseScriptStage): + def __init__(self, prob_data, TB_code, **gptkwargs) -> None: + super().__init__("stage_4b", **gptkwargs) + self.header = prob_data["header"] + signals_input_template = Stage4_SEQ.header_to_SignalTxt_template(prob_data["header"], check_en=False) + self.TB_code = TB_code + self.txt1 = Stage4b_SEQ_TXT1 + self.txt2 = Stage4b_SEQ_TXT2.replace(SIGNALTEMP_PLACEHOLDER_1, signals_input_template) + self.TB_code_out = self.TB_code + + def make_prompt(self): + self.prompt = "" + self.add_prompt_line(self.txt1) + self.add_prompt_line(self.TB_code) + self.add_prompt_line(self.txt2) + + def postprocessing(self): + self.TB_code_out = self.extract_code(self.response, "verilog")[-1] + self.TB_code_out = utils.pychecker_SEQ_TB_standardization(self.TB_code_out, self.header) + + +STAGE5_SEQ_TXT1 = """ +1. background: Your task is to verify the functional correctness of a verilog RTL module code (we call it as "DUT", device under test). This module is a sequential circuit. Our plan is to first export the signals (input and output) of the DUT under test scenarios. Then, we will use a python script to check the correctness of DUT. +2. You are in stage 5. In this stage, we already exported the signals of DUT. The signals are like below: (the signal names are real, but the values are just for example, clock signals are not included, each vector represents a new clock cycle) +%s +Here's the explanation of some special signals in signal vectors: +- "scenario": The "scenario" is not DUT's signal but to tell you the current scenario index. +- "check_en": The "check_en" signal is not from the DUT. "Check_en" is a bool value to tell you this is the time to check the output of DUT. It is related to the class method "check" (we will explain it later). After checking the output, a new scenario will start. +3. Your current task is: write a python class "GoldenDUT". This python class can represent the golden DUT (the ideal one). In your "GoldenDUT", you should do the following things: +- 3.1. write a method "def __init__(self)". Set the inner states/values of the golden DUT. These values have suffix "_reg". The initial value of these inner values is "x", but later will be digits. The "__init__" method has no input parameters except "self". +- 3.2. write a method "def load(self, signal_vector)". This method is to load the important input signals and the inner values of "GoldenDUT" shall change according to the input signals. There is no clock signal in the input signal vector, every time the "load" method is called, it means a new clock cycle. The initial values "x" should be changed according to the input signals. This method has no return value. +- 3.3. write a method "def check(self, signal_vector)". This method is to determine the expected output values and compare them with output signals from DUT. It should return True or False only. If return false, please print the error message. Hint: you can use code like "print(f"Scenario: {signal_vector['scenario']}, expected: a={a_reg}, observed a={a_observed}")" to print, suppose "a" is the output signal's name. +- 3.4. write other methods you need, they can be called by "load" or "check". +- 3.5. the input of "load" and "check" is the signal vector. The signal vector is a dictionary, the key is the signal name, the value is the signal value. +4. Other information: +- You can use binary (like 0x1101), hexadecimal (like 0x1a) or normal number format in python. +- if the bit width of one variable is limited, use bit mask to assure the correctness of the value. +- you can import numpy, math, scipy or other python libraries to help you write the python class. +5. You have the information below to help you check the correctness of DUT: +"""%(SIGNALTEMP_PLACEHOLDER_1) + +STAGE5_SEQ_TXT2 = """ +[IMPORTANT] +I will repeat the important information: +3. Your current task is: write a python class "GoldenDUT". This python class can represent the golden DUT (the ideal one). In your "GoldenDUT", you should do the following things: +- 3.1. write a method "def __init__(self)". Set the inner states/values of the golden DUT. These values have suffix "_reg". The initial value of these inner values should be digits. You can set the initial values according to information or just "0"s. The "__init__" method has no input parameters except "self". +- 3.2. write a method "def load(self, signal_vector)". This method is to load the important input signals and the inner values of "GoldenDUT" shall change according to the input signals. There is no clock signal in the input signal vector, every time the "load" method is called, it means a new clock cycle. The initial values "x" should be changed according to the input signals. This method has no return value. +- 3.3. write a method "def check(self, signal_vector)". This method is to determine the expected output values and compare them with output signals from DUT. It should return True or False only. If return false, please print the error message. Hint: you can use code like "print(f"Scenario: {signal_vector['scenario']}, expected: a={a_reg}, observed a={a_observed}")" to print, suppose "a" is the output signal's name. +- 3.4. write other methods you need, they can be called by "load" or "check". +- 3.5. the input of "load" and "check" is the signal vector. The signal vector is a dictionary, the key is the signal name, the value is the signal value. +4. Other information: +- You can use binary (like 0x1101), hexadecimal (like 0x1a) or normal number format in python. +- if the bit width of one variable is limited, use bit mask to assure the correctness of the value. +- you can import numpy, math, scipy or other python libraries to help you write the python class. + +please only reply the python codes of the python class. no other words. +""" + +STAGE5_SEQ_CODE1 = """ +def check_dut(vectors_in): + golden_dut = GoldenDUT() + failed_scenarios = [] + for vector in vectors_in: + if vector["check_en"]: + check_pass = golden_dut.check(vector) + if check_pass: + print(f"Passed; vector: {vector}") + else: + print(f"Failed; vector: {vector}") + failed_scenarios.append(vector["scenario"]) + golden_dut.load(vector) + return failed_scenarios +""" + +STAGE5_SEQ_CODE2 = """ +def SignalTxt_to_dictlist(txt:str): + signals = [] + lines = txt.strip().split("\\n") + for line in lines: + signal = {} + if line.startswith("[check]"): + signal["check_en"] = True + line = line[7:] + elif line.startswith("scenario"): + signal["check_en"] = False + else: + continue + line = line.strip().split(", ") + for item in line: + if "scenario" in item: + item = item.split(": ") + signal["scenario"] = item[1].replace(" ", "") + else: + item = item.split(" = ") + key = item[0] + value = item[1] + if ("x" not in value) and ("X" not in value) and ("z" not in value): + signal[key] = int(value) + else: + if ("x" in value) or ("X" in value): + signal[key] = 0 # used to be "x" + else: + signal[key] = 0 # used to be "z" + signals.append(signal) + return signals +with open("TBout.txt", "r") as f: + txt = f.read() +vectors_in = SignalTxt_to_dictlist(txt) +tb_pass = check_dut(vectors_in) +print(tb_pass) +""" +class Stage5_SEQ(BaseScriptStage): + """stage 5 (SEQ): generate the pychecker that receive the signals from testbench and check the correctness of DUT""" + def __init__(self, prob_data, response_stage1, response_stage3, **gptkwargs) -> None: + super().__init__("stage_5", **gptkwargs) + self.prob_data = prob_data + self.response_stage1 = response_stage1 # currently not used + self.response_stage3 = response_stage3 + self.txt1 = STAGE5_SEQ_TXT1.replace(SIGNALTEMP_PLACEHOLDER_1, utils.signal_dictlist_template(prob_data["header"], exclude_clk=True)) + self.txt2 = STAGE5_SEQ_TXT2 + self.code_tail = STAGE5_SEQ_CODE1 + STAGE5_SEQ_CODE2 + + def make_prompt(self): + self.prompt = "" + # introduction + self.add_prompt_line(self.txt1) + # problem description + self.add_prompt_line("DUT circuit problem description:") + self.add_prompt_line(self.prob_data["description"]) + # DUT header + self.add_prompt_line("The header of DUT (note the input and output signals):") + self.add_prompt_line(self.prob_data["header"]) + # python rules + self.add_prompt_line("Here is the basic rules in python for the module. It was generated in previous stage. You can use it as a reference, but you should write your own python script. This is just for your better understanding. You can use them or not in your python class") + self.add_prompt_line(self.response_stage3) + # end + self.add_prompt_line(self.txt2) + + def postprocessing(self): + # python codes + self.response = self.extract_code(self.response, "python")[-1] + self.Pychecker_code_out = self.response + self.code_tail + + +class StageChecklist(BaseScriptStage): + def __init__(self, TB_code:str, checklist_str:str, max_iter:int, **gptkwargs) -> None: + super().__init__("stage_checklist", **gptkwargs) + self.checklist = checklist_str # {"scenario 1": "xxx", "scenario 2": "xxx", ...} + self.checklist_dict = json.loads(checklist_str) + self.missing_scenarios = [] + self.max_iter = max_iter + self.TB_code_out = TB_code + self.exit = False + self.iter = 0 + self.TB_modified = False + + def make_prompt(self): + self.prompt = "" + self.add_prompt_line("please check the if the testbench code contains all the items in the checklist:") + self.add_prompt_line("testbench code here...\n") + self.add_prompt_line(self.TB_code_out + "\n") + self.add_prompt_line("please check the if the testbench code above contains all the scenarios in the checklist:") + self.add_prompt_line(self.checklist) + self.add_prompt_line("please reply 'YES' if all the items are included. If some of the items are missed in testbench, please add the missing items and reply the modified testbench code (full code).") + self.add_prompt_line("HINT: the missing scenarios may be: " + str(self.missing_scenarios)) + self.add_prompt_line("VERY IMPORTANT: please ONLY reply 'YES' or the full code modified. NEVER remove other irrelevant codes!!!") + + def postprocessing(self): + self.iter += 1 + if "YES" in self.response: + self.exit = True + else: + self.TB_modified = True + self.TB_code_out = self.extract_code(self.response, "verilog")[-1] + + def pre_check(self): + """this function is called at the beginning of run() so that the stage can be skipped if needed""" + self.missing_scenarios = [] + for key in self.checklist_dict.keys(): + if key.replace(" ", " = ") not in self.TB_code_out: + self.missing_scenarios.append(key) + + def run(self): + self.TB_modified = False + while (not self.exit) and (self.iter < self.max_iter): + self.pre_check() + if self.missing_scenarios == []: + self.exit = True + self.conversation_message += "\n[SYSTEM PRECHECK] All scenarios are included in the testbench code. You can continue to the next stage." + else: + self.make_prompt() + self.call_gpt() + self.postprocessing() diff --git a/prompt_scripts/utils.py b/prompt_scripts/utils.py new file mode 100644 index 0000000..8778e75 --- /dev/null +++ b/prompt_scripts/utils.py @@ -0,0 +1,803 @@ +""" +Description : some tool functions for prompt scripts and their stages +Author : Ruidi Qiu (r.qiu@tum.de) +Time : 2024/4/25 13:26:06 +LastEdited : 2024/9/3 20:55:11 +""" + +import math +from utils.utils import run_with_timeout +#################################### +# used by pychecker_SEQ +def extract_signals(header): + """ + - given the header of a module, extract the signals + - output format: [{"name": "signal_name", "width": "[x:x]", "type": "input/output"}, ...] + """ + def get_width_ifhave(signal): + if len(signal) > 2 and "[" in signal[-2] and "]" in signal[-2]: + # remove other parts except the [x:x] + width = signal[-2] + width = width.split("[")[1].split("]")[0] + width = "[" + width + "]" + return width + else: + return "" + signals = header.split("(")[1].split(")")[0].split(",") + signals = [signal.strip().split(" ") for signal in signals] + signals = [{"name": signal[-1], "width": get_width_ifhave(signal), "type": signal[0]} for signal in signals] + return signals + +def fdisplay_code_gen(header, ckeck_en=True): + """ + - input: head, like: + module top_module( + input clk, + input reset, + output reg [3:0] q); + - return: + - no check: $fdisplay(file, "scenario: %d, clk = %d, reset = %d, q = %d", scenario, clk, reset, q); + - check: $fdisplay(file, "[check]scenario: %d, clk = %d, reset = %d, q = %d", scenario, clk, reset, q); + """ + signals = extract_signals(header) + begining = '$fdisplay(file, "' + ending = ");" + check = "[check]" if ckeck_en else "" + middle1 = check + "scenario: %d" + middle2 = ", scenario" + middle1_signals = "" + middle2_signals = "" + for signal in signals: + middle1_signals += ", %s = %%d" % signal["name"] + middle2_signals += ", %s" % signal["name"] + middle1 += middle1_signals + '"' + middle2 += middle2_signals + return begining + middle1 + middle2 + ending + +@run_with_timeout(timeout=30) +def pychecker_SEQ_TB_standardization(code, header): + """ + - refine the TB code + - 1. patch the weird bug of gpt generated verilog code + - 2. add $fdisplay in the repeat block if not exist + - 3. split the delay to multiple #10 + - 4. add #10 in front of the second $display if there are two $display and no delay between them + - 5. add $fdisplay in front of the second #10 if there are two #10 and no $display between them + - 6. find the repeat block and change from repeat-#10-$fdisplay-#10 to #10-repeat-$fdisplay-#10 + - 7. find all the $fdisplay sentence and rewrite them in a standard format + """ + code = verilog_patch(code) + code = add_fdisplay_to_repeat(code, header) + code = split_delay_to_delays(code) + code = find_and_rewrite_fdisplay(code, header) + code = add_delay_into_2displays_or_scenarios(code) + code = refine_repeat_fdisplay(code) + code = add_display_into_2delays(code, header) + return code + +@run_with_timeout(timeout=30) +def pychecker_CMB_TB_standardization(code, header): + """ + different from pychecker_SEQ_TB_standardization, there is no timing issues in CMB + """ + code = verilog_patch(code) + code = find_and_rewrite_fdisplay(code, header) + return code + +def find_and_rewrite_fdisplay(code:str, header:str): + """ + This function is used to find all the $fdisplay sentence and rewrite them in a standard format + """ + fdisplay_check = fdisplay_code_gen(header, ckeck_en=True) + fdisplay_nocheck = fdisplay_code_gen(header, ckeck_en=False) + current_location = 0 + code_processed = "" + code_todo = code + start = 0 + end = 0 + while True: + start = code_todo[current_location:].find("$fdisplay") + if start == -1: + break + end = code_todo[start:].find(");") + start +1 + display_sentence = code_todo[start:end+1] + code_processed += code_todo[:start] + code_todo = code_todo[end+1:] + check_en = True if "[check]" in display_sentence else False + if check_en: + code_processed += fdisplay_check + else: + code_processed += fdisplay_nocheck + code = code_processed + code_todo + return code + +def add_fdisplay_to_repeat(code:str, header:str): + code_done = "" + code_todo = code + while True: + repeat_start = code_todo.find("repeat") + if repeat_start == -1: + break + # check if no display until the next scenario + next_scenario = code_todo[repeat_start:].find("$fdisplay") # it is ok even if it is -1 + if "[check]" not in code_todo[repeat_start:repeat_start+next_scenario]: + fdisplay_code = fdisplay_code_gen(header, ckeck_en=True) + " " + else: + fdisplay_code = fdisplay_code_gen(header, ckeck_en=False) + " " + # check if this repeat is single-line or multi-line + new_line = min_no_minusone(code_todo[repeat_start:].find("\n"), code_todo[repeat_start:].find("//")) + if "begin" not in code_todo[repeat_start:repeat_start+new_line]: + # single-line repeat, add begin end + repeat_end = new_line + repeat_start + after_repeat = code_todo[repeat_start:repeat_start+new_line].find(")") + 2 + repeat_start + repeat_block = code_todo[repeat_start:after_repeat] + "begin " + code_todo[after_repeat:repeat_end] + " end" + else: + repeat_end = code_todo[repeat_start:].find("end") + repeat_start + repeat_block = code_todo[repeat_start:repeat_end] + # check if there is a $fdisplay in the repeat block + if "$fdisplay" not in repeat_block: + # no fdisplay, add one in front of the first delay + delay_start = repeat_block.find("#") + # add the fdisplay before the first delay + code_done += code_todo[:repeat_start] + repeat_block[:delay_start] + fdisplay_code + repeat_block[delay_start:] + else: + code_done += code_todo[:repeat_start] + repeat_block + code_todo = code_todo[repeat_end:] + code_done += code_todo + return code_done + +# def add_delay_into_2displays(code): +# """ +# - is there are two $display and there is no delay between them, add #10 at the front of the second $display +# - two kinds of action: insert #10 into two displays; insert #10 into one display and +# """ +# code_todo = code +# code_done = "" +# while True: +# if "$fdisplay" in code_todo: +# # find the next $fdisplay +# start_first = code_todo.find("$fdisplay") +# end_first = code_todo[start_first:].find(");") + start_first + 2 +# start_second = code_todo[end_first:].find("$fdisplay") + end_first +# if start_second == -1: +# break +# # check if there is a delay between them +# subcode = code_todo[end_first:start_second] +# delay_exist = ("#" in subcode) +# if (not delay_exist): +# code_done += code_todo[:end_first] + " #10; " +# else: +# code_done += code_todo[:end_first] +# code_todo = code_todo[end_first:] +# else: +# code_done += code_todo +# break +# return code_done + +def add_delay_into_2displays_or_scenarios(code): + """ + - is there are two $display and there is no delay between them, add #10 at the front of the second $display + - three cases: + - two displays: if no delay, insert + - display and scenario: if no delay, insert + - scenario and display: delete the delay (not sure if we should do, to be continue) + """ + code_todo = code + code_done = "" + new_scenario_next = True + while True: + if "$fdisplay" in code_todo: + # find the next $fdisplay or scenario + if new_scenario_next: + start_first = code_todo.find("scenario =") + end_first = code_todo[start_first:].find(";") + start_first + 1 + new_scenario_next = False + new_scenario_now = True + else: + start_first = code_todo.find("$fdisplay") + end_first = code_todo[start_first:].find(");") + start_first + 2 + new_scenario_now = False + # check scenario + start_scenario = code_todo[end_first:].find("scenario =" ) + start_second = code_todo[end_first:].find("$fdisplay") + if start_second == -1: + code_done += code_todo + break + if not (start_scenario == -1) and (start_scenario < start_second): + # next is a new scenario + start_second = start_scenario + new_scenario_next = True + start_second += end_first + # check and insert delay + subcode = code_todo[end_first:start_second] + if new_scenario_now: + # it is ok if there is no delay between scenario and display because delay already exists behind the last scenario + code_done += code_todo[:end_first] + else: + if (not ("#" in subcode)): + code_done += code_todo[:end_first] + " #10; " + else: + code_done += code_todo[:end_first] + code_todo = code_todo[end_first:] + else: + code_done += code_todo + break + return code_done + +def refine_repeat_fdisplay(code:str): + """ + - good repeat block: $display->#10->repeat{$display->#10}n->$display->#10 + - bad repeat block: $display->repeat{#10->$display->#10}n->$display->#10 + - this code standardization is newly added in AutoBench2 + """ + # capture the repeat block + code_todo = code + code_done = "" + while "repeat" in code_todo: + repeat_start = code_todo.find("repeat") + repeat_end = code_todo[repeat_start:].find("end") + repeat_start + repeat_block = code_todo[repeat_start:repeat_end] + # check if the repeat block satisfies the condition + delay_amount_in_repeat = repeat_block.count("#10") + display_amount_in_repeat = repeat_block.count("$fdisplay") + if delay_amount_in_repeat == display_amount_in_repeat + 1: + # then we need to move one delay out + first_delay_pos = repeat_block.find("#10") + first_display_pos = repeat_block[first_delay_pos:].find("$fdisplay") + first_delay_pos + repeat_block = repeat_block[:first_delay_pos] + repeat_block[first_display_pos:] + before_repeat = code_todo[:repeat_start] + before_repeat_last_newline = before_repeat.rfind("\n") + # replace the last "\n" with " #10;\n" + before_repeat = before_repeat[:before_repeat_last_newline] + " #10;\n" + before_repeat[before_repeat_last_newline+1:] + code_done += before_repeat + repeat_block + # code_done += code_todo[:repeat_start] + repeat_block + code_todo = code_todo[repeat_end:] + elif delay_amount_in_repeat + 1 == display_amount_in_repeat: + # then we need to move one #10 in + # we add a #10 before the first $fdisplay + first_display_pos = repeat_block.find("$fdisplay") + repeat_block = repeat_block[:first_display_pos] + "#10; " + repeat_block[first_display_pos:] + # then we delete the last #10 before the repeat block + before_repeat = code_todo[:repeat_start] + before_repeat_last_delay = before_repeat.rfind("#10") + before_repeat = before_repeat[:before_repeat_last_delay] + before_repeat[before_repeat_last_delay+4:] + code_done += before_repeat + repeat_block + code_todo = code_todo[repeat_end:] + else: + code_done += code_todo[:repeat_start] + repeat_block + code_todo = code_todo[repeat_end:] + code_done += code_todo + return code_done + +def add_display_into_2delays(code:str, header:str=None): + """if there are two #10 and there is no $fdisplay between them, add $fdisplay at the front of the second #10""" + def find_display(code:str): + load = "" + check = "" + start = code.find("$fdisplay") + end = code[start:].find(")") + start + first_display = code[start:end+1] + ";" + if "[check]" in first_display: + check = first_display + load = check.replace("[check]", "") + else: + load = first_display + check = load.replace('"scenario: ', '"[check]scenario: ') + return load, check + if header is None: + load, check = find_display(code) + else: + load = fdisplay_code_gen(header, ckeck_en=False) + check = fdisplay_code_gen(header, ckeck_en=True) + code_parts = code.split("#10") + if len(code_parts) >= 2: + # make sure there are at least two #10 + for idx, subcode in enumerate(code_parts[:-2]): + real_idx = idx + if "$fdisplay" not in subcode: + code_parts[real_idx] += load + " " + return "#10".join(code_parts) + +def split_delay_to_delays(code:str): + # start from the first Scenario/scenario + start = max(code.find("scenario"), code.find("Scenario")) + code_before = code[:start] + code = code[start:] + code = code.split("#") + for idx, subcode in enumerate(code): + if idx != 0: + # find the delay number; i.e., "20 asdbuaw" return "20" + digit = "" + for char in subcode: + if char.isdigit(): + digit += char + else: + break + if digit and (digit != "10"): + delay_time = int(digit) + delay10_num = math.ceil(delay_time / 10.0) + # replace the original delay with multiple #10 + new_delay = "#10; " * delay10_num + new_delay = new_delay[1:-2] + code[idx] = new_delay + subcode[len(digit):] + return code_before + "#".join(code) + +def verilog_patch(vcode:str): + """ + here is a patch for a weird bug of gpt generated verilog code + the bug is "initial begin ... }" or "initial { ... }" + """ + if r"{\n" in vcode: + vcode = vcode.replace(r"{\n", r"begin\n") + # scan the code line by line + vcode_lines = vcode.split("\n") + endmodule = False + for i, line in enumerate(vcode_lines): + line_temp = line.replace(" ", "") + if line_temp == "}": + vcode_lines[i] = line.replace("}", "end") + if "endmodule" in line_temp: + if endmodule: + vcode_lines[i] = line.replace("endmodule", "") + else: + endmodule = True + return "\n".join(vcode_lines) + +@run_with_timeout(timeout=30) +def circuit_type_by_code(code:str): + """ + - input: code + - output: "CMB" or "SEQ" + """ + def string_to_words(string:str): + words = string.split(" ") + words = [word for word in words if word != ""] + return words + # _SEQ_exit_pos = 0 # for debug + circuit_type = "CMB" # will be changed to "SEQ" if sequential + if "always" in code: + while True: + always_start = code.find("always") + if always_start == -1: + break + if code[always_start-1] not in [" ", "\n", "\t", ";"]: + code = code[always_start+6:] + continue + elif code[always_start+6] not in [" ", "@"]: + # check always_ff, _comb and _latch + if code[always_start+6] == "_": + always_word = code[always_start:code[always_start:].find(" ")+always_start] + if always_word == "always_ff" or always_word == "always_latch": + circuit_type = "SEQ" + break + code = code[always_start+6:] + continue + # check if there is a begin till next ";" + next_semicolon = code[always_start:].find(";") + if "begin" in code[always_start:always_start+next_semicolon]: + has_begin = True + always_end = code[always_start:].find("end") + always_start + else: + has_begin = False + always_end = next_semicolon + always_start + always_block = code[always_start:always_end] + # currently we use a naive way to check if the always block is sequential or not; will be improved in the future + # check if () exist for the sensitivity list + at_pos = always_block.find("@") + # check the first not-" " character after "@" + char_pos = at_pos + for char in always_block[at_pos+1:]: + char_pos += 1 + if char != " ": + break + has_bracket = True if char == "(" else False + signal_list = [] + if has_bracket: + sensitivity_list = always_block[always_block.find("(")+1:always_block.find(")")] + sensitivity_list = sensitivity_list.split(",") + for signal in sensitivity_list: + # get none-space words: + signal_seg = string_to_words(signal) + if len(signal_seg) > 1 and ("posedge" in signal_seg or "negedge" in signal_seg): + circuit_type = "SEQ" + # _SEQ_exit_pos = 1 + break + signal_list.append(signal_seg[-1]) + else: # no bracket, always @ a begin xxx = xxx end; + sensitivity_list_end = always_block[char_pos:].find(" ") + sensitivity_signal = always_block[char_pos:char_pos+sensitivity_list_end] + signal_list.append(sensitivity_signal) + if "*" in signal_list: + code = code[always_end:] + continue + if circuit_type == "SEQ": + # _SEQ_exit_pos = 2 + break + else: + break_always_block = string_to_words(always_block) + if "<=" in break_always_block: + circuit_type = "SEQ" + # currently we use a naive way. Following codes are skipped + # check_next_signal = False + # for seg in break_always_block: + # if check_next_signal: + # if seg not in signal_list: + # circuit_type = "SEQ" + # break + # if "=" in seg: + # check_next_signal = True + # else: + # check_next_signal = False + if circuit_type == "SEQ": + # _SEQ_exit_pos = 3 + break + code = code[always_end:] + return circuit_type + +class given_TB: + def __init__(self, header) -> None: + """ + 1. initialize sim time, module testbench and signals + 2. initialize "integer file, scenario;" + 3. instantiate the DUT + 4. clock generation (if have) + 5. scenario based test + 6. endmodule + """ + self.header = header + self.signals = extract_signals(self.header) + self.TB_code_head = "" + self.TB_code_head += "`timescale 1ns / 1ps\nmodule testbench;\n" + self.TB_code_head += self.initial_signals(self.signals) + "\n" + self.TB_code_head += "integer file, scenario;\n" + self.TB_code_head += "// DUT instantiation\n" + self.TB_code_head += self.instantiate_module_by_signals("top_module", "DUT", self.signals) + "\n" + self.TB_code_head += self.clock_generation() + self.TB_code_head += '\ninitial begin\n file = $fopen("TBout.txt", "w");\nend\n' + # self.TB_code_test = '// Test scenarios\ninitial begin\n file = $fopen("TBout.txt", "w");\n\n // write your codes here\n\n $fclose(file);\n $finish;\nend\n' + self.TB_code_test = '// Scenario Based Test\ninitial begin\n\n // write your scenario checking codes here, according to scenario information\n\n $fclose(file);\n $finish;\nend\n' + self.TB_code_tail = "\nendmodule\n" + + def gen_template(self): + return self.TB_code_head + self.TB_code_test + self.TB_code_tail + + def clock_generation(self): + clk_en = False + for signal in self.signals: + if signal["name"] in ["clk", "clock"]: + clk_en = True + clk = signal["name"] + break + if not clk_en: + return "" + else: + return "// Clock generation\ninitial begin\n [clk] = 0;\n forever #5 [clk] = ~[clk];\nend\n".replace("[clk]", clk) + + @staticmethod + def initial_signals(signals): + """ + - this function is used to initialize signals + """ + initial_str = "" + for signal in signals: + if signal["type"] == "input": + initial_str += f"reg {signal['width']} {signal['name']};\n" + else: + initial_str += f"wire {signal['width']} {signal['name']};\n" + return initial_str + + @staticmethod + def instantiate_module_by_signals(module_name, instantiate_name, signals): + """ + - this function is used to instantiate a module by signals + - the signals should be like [{"name": "a", "width": "[3:0]", "type": "input"}, ...] + """ + instantiate_str = f"{module_name} {instantiate_name} (\n" + for signal in signals: + if signal["width"]: + instantiate_str += f"\t.{signal['name']}({signal['name']}),\n" + else: + instantiate_str += f"\t.{signal['name']}({signal['name']}),\n" + instantiate_str = instantiate_str[:-2] + "\n);" + return instantiate_str + +# used by stage 5 +def signal_dictlist_template(header:str, exclude_clk:bool=False, use_check_en:bool = True) -> str: + """ + for the automatic generation of signals in testbench + target: given the DUT header, generate the signal output template + eg: if we have a DUT header like "module DUT(input a, b, c, output d, e);", the signal output template should be like "[{"check_en": 0, "scenario": 1, "a": 1, "b": 0, "c":1, "d": 0, "e": 0}, {"check_en": 1, "scenario": 1, "a": 0, "b": 0, "c":1, "d": 0, "e": 0}]" + """ + signals_dictlist1 = header_to_dictlist(header, exclude_clk=exclude_clk, use_check_en=use_check_en) + signals_dictlist2 = header_to_dictlist(header, exclude_clk=exclude_clk, use_check_en=use_check_en) + signals_dictlist3 = header_to_dictlist(header, check_en=True, exclude_clk=exclude_clk, use_check_en=use_check_en) + signals_dictlist = signals_dictlist1 + signals_dictlist2 + signals_dictlist3 + return str(signals_dictlist) + +def header_to_dictlist(header:str, value=1, scenario_idx=1, check_en = False, exclude_clk:bool=False, use_check_en:bool = True) -> str: + """ + - header: the header of DUT + - template_scenario_idx: the scenario index in the template + - signal_value: the value of the signal in the template + - only: None: both input signal and output signal; "input": only input signal; "output": only output signal + - from header to signals in txt + - for the automatic generation of signals in testbench + - target: given the DUT header, generate the signal output template + - eg: if we have a DUT header like "module DUT(input clk, load, data, output q);", the signal output template should be like "$fdisplay(file, "scenario: %d, clk = %d, load = %d, data = %d, q = %d", scenario, clk, load, data, q);" + """ + signals = extract_signals(header) + if exclude_clk: + signals = [signal for signal in signals if signal["name"] not in ["clk", "clock"]] + dict_out = {} + dict_list_out = [dict_out] + if use_check_en: + dict_out["check_en"] = check_en + dict_out["scenario"] = scenario_idx + for signal in signals: + dict_out[signal["name"]] = value + return dict_list_out + + +def signal_dictlist_template_CMB(header:str, exclude_clk:bool=False) -> str: + """ + for the automatic generation of signals in testbench + target: given the DUT header, generate the signal output template + eg: if we have a DUT header like "module DUT(input a, b, c, output d, e);", the signal output template should be like "[{"check_en": 0, "scenario": 1, "a": 1, "b": 0, "c":1, "d": 0, "e": 0}, {"check_en": 1, "scenario": 1, "a": 0, "b": 0, "c":1, "d": 0, "e": 0}]" + """ + signals_dictlist1 = header_to_dictlist(header, exclude_clk=exclude_clk) + + return str(signals_dictlist1) + +def header_to_dictlist_CMB(header:str, value=1, scenario_idx=1, exclude_clk:bool=False) -> str: + """ + - header: the header of DUT + - template_scenario_idx: the scenario index in the template + - signal_value: the value of the signal in the template + - only: None: both input signal and output signal; "input": only input signal; "output": only output signal + - from header to signals in txt + - for the automatic generation of signals in testbench + - target: given the DUT header, generate the signal output template + - eg: if we have a DUT header like "module DUT(input clk, load, data, output q);", the signal output template should be like "$fdisplay(file, "scenario: %d, clk = %d, load = %d, data = %d, q = %d", scenario, clk, load, data, q);" + """ + signals = extract_signals(header) + if exclude_clk: + signals = [signal for signal in signals if signal["name"] not in ["clk", "clock"]] + dict_out = {} + dict_list_out = [dict_out] + # dict_out["scenario"] = scenario_idx + for signal in signals: + dict_out[signal["name"]] = value + return dict_list_out + +def min_no_minusone(a, b): + if a == -1: + return b + if b == -1: + return a + return min(a, b) + +if __name__ == "__main__": + code = """ +`timescale 1ns / 1ps +module testbench; +reg clk; +reg areset; +reg x; +wire z; + +integer file, scenario; +// DUT instantiation +top_module DUT ( + .clk(clk), + .areset(areset), + .x(x), + .z(z) +); +// Clock generation +initial begin + clk = 0; + forever #5 clk = ~clk; +end + +initial begin + file = $fopen("TBout.txt", "w"); +end +// Scenario Based Test +initial begin + // Scenario 1 + scenario = 1; + areset = 1; + x = 0; + repeat(2) begin + $fdisplay(file, "[check]scenario: %d, clk = %d, areset = %d, x = %d, z = %d", scenario, clk, areset, x, z); + #10; + end + areset = 0; + repeat(4) begin + x = scenario % 2; + scenario = scenario / 2; + $fdisplay(file, "[check]scenario: %d, clk = %d, areset = %d, x = %d, z = %d", 1, clk, areset, x, z); + #10; + end + + // Scenario 2 + scenario = 2; + areset = 1; + x = 0; + repeat(3) begin + $fdisplay(file, "[check]scenario: %d, clk = %d, areset = %d, x = %d, z = %d", scenario, clk, areset, x, z); + #10; + end + areset = 0; + repeat(8) begin + x = scenario % 2; + scenario = scenario / 2; + $fdisplay(file, "[check]scenario: %d, clk = %d, areset = %d, x = %d, z = %d", 2, clk, areset, x, z); + #10; + end + areset = 1; + $fdisplay(file, "[check]scenario: %d, clk = %d, areset = %d, x = %d, z = %d", 2, clk, areset, x, z); + #10; + areset = 0; + repeat(4) begin + x = 1; + $fdisplay(file, "[check]scenario: %d, clk = %d, areset = %d, x = %d, z = %d", 2, clk, areset, x, z); + #10; + end + + // Scenario 3 + scenario = 3; + areset = 0; + repeat(3) begin + x = scenario % 2; + scenario = scenario / 2; + $fdisplay(file, "[check]scenario: %d, clk = %d, areset = %d, x = %d, z = %d", 3, clk, areset, x, z); + #10; + end + areset = 1; + $fdisplay(file, "[check]scenario: %d, clk = %d, areset = %d, x = %d, z = %d", 3, clk, areset, x, z); + #10; + areset = 0; + repeat(3) begin + x = scenario % 2; + scenario = scenario / 2; + $fdisplay(file, "[check]scenario: %d, clk = %d, areset = %d, x = %d, z = %d", 3, clk, areset, x, z); + #10; + end + + // Scenario 4 + scenario = 4; + areset = 1; + x = 0; + $fdisplay(file, "[check]scenario: %d, clk = %d, areset = %d, x = %d, z = %d", scenario, clk, areset, x, z); + #10; + areset = 0; + repeat(3) begin + x = scenario % 2; + scenario = scenario / 2; + $fdisplay(file, "[check]scenario: %d, clk = %d, areset = %d, x = %d, z = %d", 4, clk, areset, x, z); + #10; + end + x = 0; + repeat(2) begin + $fdisplay(file, "[check]scenario: %d, clk = %d, areset = %d, x = %d, z = %d", 4, clk, areset, x, z); + #10; + end + scenario = 25; // 11001 in binary + repeat(5) begin + x = scenario % 2; + scenario = scenario / 2; + $fdisplay(file, "[check]scenario: %d, clk = %d, areset = %d, x = %d, z = %d", 4, clk, areset, x, z); + #10; + end + + // Scenario 5 + scenario = 5; + areset = 0; + repeat(8) begin + x = scenario % 2; + scenario = scenario / 2; + $fdisplay(file, "[check]scenario: %d, clk = %d, areset = %d, x = %d, z = %d", 5, clk, areset, x, z); + #10; + end + areset = 1; + $fdisplay(file, "[check]scenario: %d, clk = %d, areset = %d, x = %d, z = %d", 5, clk, areset, x, z); + #10; + areset = 0; + scenario = 170; // 10101010 in binary + repeat(8) begin + x = scenario % 2; + scenario = scenario / 2; + $fdisplay(file, "[check]scenario: %d, clk = %d, areset = %d, x = %d, z = %d", 5, clk, areset, x, z); + #10; + end + + // Scenario 6 + scenario = 6; + areset = 1; + x = 0; + repeat(4) begin + $fdisplay(file, "[check]scenario: %d, clk = %d, areset = %d, x = %d, z = %d", scenario, clk, areset, x, z); + #10; + end + areset = 0; + repeat(8) begin + x = scenario % 2; + scenario = scenario / 2; + $fdisplay(file, "[check]scenario: %d, clk = %d, areset = %d, x = %d, z = %d", 6, clk, areset, x, z); + #10; + end + x = 1; + repeat(5) begin + $fdisplay(file, "[check]scenario: %d, clk = %d, areset = %d, x = %d, z = %d", 6, clk, areset, x, z); + #10; + end + + // Scenario 7 + scenario = 7; + areset = 0; + x = 0; + repeat(5) begin + $fdisplay(file, "[check]scenario: %d, clk = %d, areset = %d, x = %d, z = %d", scenario, clk, areset, x, z); + #10; + end + x = 1; + repeat(5) begin + $fdisplay(file, "[check]scenario: %d, clk = %d, areset = %d, x = %d, z = %d", scenario, clk, areset, x, z); + #10; + end + areset = 1; + repeat(2) begin + $fdisplay(file, "[check]scenario: %d, clk = %d, areset = %d, x = %d, z = %d", scenario, clk, areset, x, z); + #10; + end + areset = 0; + scenario = 10; // 01010 in binary + repeat(5) begin + x = scenario % 2; + scenario = scenario / 2; + $fdisplay(file, "[check]scenario: %d, clk = %d, areset = %d, x = %d, z = %d", 7, clk, areset, x, z); + #10; + end + + // Scenario 8 + scenario = 8; + areset = 1; + x = 0; + $fdisplay(file, "[check]scenario: %d, clk = %d, areset = %d, x = %d, z = %d", scenario, clk, areset, x, z); + #10; + areset = 0; + scenario = 455; // 111000111 in binary + repeat(9) begin + x = scenario % 2; + scenario = scenario / 2; + $fdisplay(file, "[check]scenario: %d, clk = %d, areset = %d, x = %d, z = %d", 8, clk, areset, x, z); + #10; + end + areset = 1; + $fdisplay(file, "[check]scenario: %d, clk = %d, areset = %d, x = %d, z = %d", 8, clk, areset, x, z); + #10; + areset = 0; + scenario = 56; // 000111000 in binary + repeat(9) begin + x = scenario % 2; + scenario = scenario / 2; + $fdisplay(file, "[check]scenario: %d, clk = %d, areset = %d, x = %d, z = %d", 8, clk, areset, x, z); + #10; + end + + $fclose(file); + $finish; +end + +endmodule""" + + header = """module top_module ( + input clk, + input areset, + input x, + output z +);""" + + code = pychecker_SEQ_TB_standardization(code, header) + print(code) \ No newline at end of file diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..f412830 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,6 @@ +openai>=1.0.0 +anthropic>=0.0.8 +httpx>=0.25.0 +loguru>=0.7.0 +pyyaml>=6.0 +tiktoken>=0.5.0 diff --git a/results/example_mul.json b/results/example_mul.json new file mode 100644 index 0000000..8aa8f3a --- /dev/null +++ b/results/example_mul.json @@ -0,0 +1,9 @@ +{ + "task_id": "example_mul", + "stage": "TBeval", + "pass": null, + "coverage": 0.0, + "cga_enabled": true, + "Eval1_pass": true, + "full_pass": null +} \ No newline at end of file diff --git a/run_tbgen.py b/run_tbgen.py new file mode 100644 index 0000000..2229bc4 --- /dev/null +++ b/run_tbgen.py @@ -0,0 +1,209 @@ +""" +TB Generator - 根据DUT和项目要求生成Testbench(完整版) +支持多阶段流程: TBgen → TBsim → TBcheck → CGA → TBeval + +用法: + from run_tbgen import generate_tb + tb_path, result = generate_tb( + dut_code="module example(...); endmodule", + description="项目描述", + header="module example(input clk, ...);", + task_id="my_task", + model="qwen-max" + ) +""" +import os +import sys +from pathlib import Path + +# 添加项目路径 +PROJECT_ROOT = Path(__file__).parent +sys.path.insert(0, str(PROJECT_ROOT)) + + +# 延迟导入和初始化,避免单例顺序问题 +_config_instance = None +_auto_logger_instance = None + +def _ensure_init(): + """确保Config和AutoLogger正确初始化""" + global _config_instance, _auto_logger_instance + + if _config_instance is None: + # 创建临时配置文件 + _temp_config_path = os.path.join(PROJECT_ROOT, "config", "custom.yaml") + _config_content = """ +run: + mode: 'autoline' +gpt: + model: "qwen-max" + key_path: "config/key_API.json" +save: + en: True + root: "./output/" +autoline: + cga: + enabled: True + max_iter: 10 + promptscript: "pychecker" + onlyrun: "TBgensimeval" +""" + with open(_temp_config_path, "w") as f: + f.write(_config_content) + + # 必须在导入autoline之前创建Config + from config import Config + _config_instance = Config(_temp_config_path) + + # 初始化AutoLogger + from loader_saver import AutoLogger + _auto_logger_instance = AutoLogger() + + return _config_instance + + +def _create_config_for_task(task_id, model, enable_cga, cga_iter): + """为特定任务创建配置""" + config_path = os.path.join(PROJECT_ROOT, "config", "custom.yaml") + + config_content = f""" +run: + mode: 'autoline' +gpt: + model: "{model}" + key_path: "config/key_API.json" +save: + en: True + root: "{os.path.join(PROJECT_ROOT, 'output', task_id)}/" +autoline: + cga: + enabled: {enable_cga} + max_iter: {cga_iter} + promptscript: "pychecker" + onlyrun: "TBgensimeval" +""" + with open(config_path, "w") as f: + f.write(config_content) + + from config import Config + return Config(config_path) + + +class TBGenerator: + """完整的TB生成器,支持多阶段流程""" + + def __init__(self, api_key_path="config/key_API.json", model="qwen-max"): + self.api_key_path = api_key_path + self.model = model + + def generate(self, dut_code, description, header, task_id="test", + enable_cga=True, cga_iter=10): + """ + 生成Testbench(完整流程) + + 参数: + dut_code: str, DUT的Verilog代码 + description: str, 项目描述/需求 + header: str, DUT的module header + task_id: str, 任务ID + enable_cga: bool, 是否启用CGA优化 + cga_iter: int, CGA最大迭代次数 + + 返回: + dict: 包含TB代码、评估结果等 + """ + # 确保单例初始化 + _ensure_init() + + # 导入autoline(现在可以安全导入了) + from autoline.TB_autoline import AutoLine_Task + + # 构建prob_data(符合HDLBitsProbset格式) + prob_data = { + "task_id": task_id, + "task_number": 1, + "description": description, + "header": header, + "module_code": dut_code, + "testbench": None, + "mutants": [], + "llmgen_RTL": [] + } + + # 为任务创建配置 + config = _create_config_for_task(task_id, self.model, enable_cga, cga_iter) + + # 创建任务并运行 + task = AutoLine_Task(prob_data, config) + task.run() + + return { + "TB_code_v": task.TB_code_v, + "TB_code_py": task.TB_code_py, + "run_info": task.run_info, + "cga_coverage": task.cga_coverage, + "full_pass": task.full_pass + } + + +def generate_tb(dut_code, description, header, task_id, + api_key_path="config/key_API.json", + model="qwen-max", + enable_cga=True, + output_dir="./output"): + """ + 便捷函数:生成TB并保存 + + 参数: + dut_code: str, DUT代码 + description: str, 项目描述 + header: str, module header + task_id: str, 任务ID + api_key_path: str, API密钥路径 + model: str, 使用的模型 + enable_cga: bool, 是否启用CGA + output_dir: str, 输出目录 + + 返回: + tuple: (TB文件路径, 结果字典) + """ + generator = TBGenerator(api_key_path, model) + result = generator.generate(dut_code, description, header, task_id, enable_cga) + + os.makedirs(output_dir, exist_ok=True) + tb_path = os.path.join(output_dir, f"{task_id}_tb.v") + from loader_saver import save_code + save_code(result["TB_code_v"], tb_path) + + return tb_path, result + + +if __name__ == "__main__": + # 示例用法 + example_dut = """ +module example( + input clk, + input rst, + input [7:0] a, + input [7:0] b, + output [15:0] y +); + assign y = a * b; +endmodule +""" + + example_desc = "一个8位乘法器,输入两个8位无符号数,输出16位乘积" + + example_header = "module example(input clk, input rst, input [7:0] a, input [7:0] b, output [15:0] y);" + + print("Generating TB for example multiplier...") + tb_path, result = generate_tb( + dut_code=example_dut, + description=example_desc, + header=example_header, + task_id="example_mul", + model="qwen-max" + ) + print(f"TB saved to: {tb_path}") + print(f"Coverage: {result.get('cga_coverage', 0)}") + print(f"Full Pass: {result.get('full_pass', False)}") diff --git a/saves/0330~0405/20260330_154844/20260330_154844.log b/saves/0330~0405/20260330_154844/20260330_154844.log new file mode 100644 index 0000000..e9ae16a --- /dev/null +++ b/saves/0330~0405/20260330_154844/20260330_154844.log @@ -0,0 +1,255 @@ + +---------------custom config-------------- +run: + version: 2.0 + author: Ruidi Qiu - Technical University of Munich + time: None + custom_path: None + mode: qwen-max +save: + en: True + root: None + pub: + prefix: None + dir: saves/$weekrange$/ + subdir: + log: + en: True + dir: logs/ + notes: None + cfg_pmode: iwantall + debug_en: False + level: TRACE + message: + en: True + dir: messages/ + format: json + iverilog: + en: True + subdir: ivcode_nodebug +load: + prompt: + path: config/initial_prompts/prompt1.txt + pick_idx: [] + stage_template: + path: config/templates/stage_template0301.txt +gpt: + model: 4o + key_path: config/key_API.json + temperature: None + json_mode: False + chatgpt: + start_form: chat + one_time_talk: False + rtlgen_model: None +iverilog: + dir: + task_id: +autoline: + result_path: results + cga: + enabled: True + max_iter: 10 + target_coverage: 100.0 + probset: + path: None + mutant_path: None + gptgenRTL_path: None + more_info_paths: [] + only: ['lemmings3', 'lemmings4', 'ece241_2013_q8', '2014_q3fsm', 'm2014_q6', 'review2015_fsm', 'rule110', 'fsm_ps2'] + exclude: [] + exclude_json: None + filter: [{}] + checklist: + max: 3 + debug: + max: 5 + reboot: 1 + py_rollback: 2 + onlyrun: None + promptscript: None + timeout: 300 + TBcheck: + rtl_num: 20 + correct_max: 3 + discrim_mode: col_full_wrong + correct_mode: naive + rtl_compens_en: True + rtl_compens_max_iter: 3 + itermax: 10 + update_desc: False + save_compile: True + save_finalcodes: True + error_interruption: False +------------------------------------------ +------config info (custom + default)------ +run: + version: 2.0 + author: Ruidi Qiu - Technical University of Munich + time: 20260330_154844 + custom_path: /home/zhang/CorrectBench/TBgen_App/config/default.yaml + mode: qwen-max + hostname: localhost + pid: 919939 + pyversion: 3.12.3 (main, Mar 3 2026, 12:15:18) [GCC 13.3.0] +save: + en: True + root: None + pub: + prefix: None + dir: saves/0330~0405/ + subdir: + log: + en: True + dir: logs/ + notes: None + cfg_pmode: iwantall + debug_en: False + level: TRACE + message: + en: True + dir: messages/ + format: json + iverilog: + en: True + subdir: ivcode_nodebug +load: + prompt: + path: config/initial_prompts/prompt1.txt + pick_idx: [] + stage_template: + path: config/templates/stage_template0301.txt +gpt: + model: gpt-4o-2024-08-06 + key_path: config/key_API.json + temperature: None + json_mode: False + chatgpt: + start_form: chat + one_time_talk: False + rtlgen_model: gpt-4o-2024-08-06 +iverilog: + dir: + task_id: +autoline: + result_path: results + cga: + enabled: True + max_iter: 10 + target_coverage: 100.0 + probset: + path: None + mutant_path: None + gptgenRTL_path: None + more_info_paths: [] + only: ['lemmings3', 'lemmings4', 'ece241_2013_q8', '2014_q3fsm', 'm2014_q6', 'review2015_fsm', 'rule110', 'fsm_ps2'] + exclude: [] + exclude_json: None + filter: [{}] + checklist: + max: 3 + debug: + max: 5 + reboot: 1 + py_rollback: 2 + onlyrun: None + promptscript: None + timeout: 300 + TBcheck: + rtl_num: 20 + correct_max: 3 + discrim_mode: col_full_wrong + correct_mode: naive + rtl_compens_en: True + rtl_compens_max_iter: 3 + itermax: 10 + update_desc: False + save_compile: True + save_finalcodes: True + error_interruption: False +_initialized: True +------------------------------------------ + +--------------default config-------------- +run: + version: 2.0 + author: Ruidi Qiu - Technical University of Munich + time: None + custom_path: None + mode: qwen-max +save: + en: True + root: None + pub: + prefix: None + dir: saves/$weekrange$/ + subdir: + log: + en: True + dir: logs/ + notes: None + cfg_pmode: iwantall + debug_en: False + level: TRACE + message: + en: True + dir: messages/ + format: json + iverilog: + en: True + subdir: ivcode_nodebug +load: + prompt: + path: config/initial_prompts/prompt1.txt + pick_idx: [] + stage_template: + path: config/templates/stage_template0301.txt +gpt: + model: 4o + key_path: config/key_API.json + temperature: None + json_mode: False + chatgpt: + start_form: chat + one_time_talk: False + rtlgen_model: None +iverilog: + dir: + task_id: +autoline: + result_path: results + cga: + enabled: True + max_iter: 10 + target_coverage: 100.0 + probset: + path: None + mutant_path: None + gptgenRTL_path: None + more_info_paths: [] + only: ['lemmings3', 'lemmings4', 'ece241_2013_q8', '2014_q3fsm', 'm2014_q6', 'review2015_fsm', 'rule110', 'fsm_ps2'] + exclude: [] + exclude_json: None + filter: [{}] + checklist: + max: 3 + debug: + max: 5 + reboot: 1 + py_rollback: 2 + onlyrun: None + promptscript: None + timeout: 300 + TBcheck: + rtl_num: 20 + correct_max: 3 + discrim_mode: col_full_wrong + correct_mode: naive + rtl_compens_en: True + rtl_compens_max_iter: 3 + itermax: 10 + update_desc: False + save_compile: True + save_finalcodes: True + error_interruption: False +------------------------------------------ diff --git a/saves/0330~0405/20260330_155017/20260330_155017.log b/saves/0330~0405/20260330_155017/20260330_155017.log new file mode 100644 index 0000000..2cc9541 --- /dev/null +++ b/saves/0330~0405/20260330_155017/20260330_155017.log @@ -0,0 +1,204 @@ + +---------------custom config-------------- +run: + mode: autoline +gpt: + model: qwen-max + key_path: config/key_API.json +save: + en: True + root: ./output/ +autoline: + cga: + enabled: True + max_iter: 10 + promptscript: pychecker + onlyrun: TBgensimeval +------------------------------------------ +------config info (custom + default)------ +run: + version: 2.0 + author: Ruidi Qiu - Technical University of Munich + time: 20260330_155017 + custom_path: /home/zhang/CorrectBench/TBgen_App/config/custom.yaml + mode: autoline + hostname: localhost + pid: 928959 + pyversion: 3.12.3 (main, Mar 3 2026, 12:15:18) [GCC 13.3.0] +save: + en: True + root: ./output/ + pub: + prefix: None + dir: saves/0330~0405/ + subdir: + log: + en: True + dir: logs/ + notes: None + cfg_pmode: iwantall + debug_en: False + level: TRACE + message: + en: True + dir: messages/ + format: json + iverilog: + en: True + subdir: ivcode_nodebug +load: + prompt: + path: config/initial_prompts/prompt1.txt + pick_idx: [] + stage_template: + path: config/templates/stage_template0301.txt +gpt: + model: qwen-max + key_path: config/key_API.json + temperature: None + json_mode: False + chatgpt: + start_form: chat + one_time_talk: False + rtlgen_model: qwen-max +iverilog: + dir: + task_id: +autoline: + result_path: results + cga: + enabled: True + max_iter: 10 + target_coverage: 100.0 + probset: + path: None + mutant_path: None + gptgenRTL_path: None + more_info_paths: [] + only: ['lemmings3', 'lemmings4', 'ece241_2013_q8', '2014_q3fsm', 'm2014_q6', 'review2015_fsm', 'rule110', 'fsm_ps2'] + exclude: [] + exclude_json: None + filter: [{}] + checklist: + max: 3 + debug: + max: 5 + reboot: 1 + py_rollback: 2 + onlyrun: TBgensimeval + promptscript: pychecker + timeout: 300 + TBcheck: + rtl_num: 20 + correct_max: 3 + discrim_mode: col_full_wrong + correct_mode: naive + rtl_compens_en: True + rtl_compens_max_iter: 3 + itermax: 10 + update_desc: False + save_compile: True + save_finalcodes: True + error_interruption: False +_initialized: True +------------------------------------------ + +--------------default config-------------- +run: + version: 2.0 + author: Ruidi Qiu - Technical University of Munich + time: None + custom_path: None + mode: qwen-max +save: + en: True + root: None + pub: + prefix: None + dir: saves/$weekrange$/ + subdir: + log: + en: True + dir: logs/ + notes: None + cfg_pmode: iwantall + debug_en: False + level: TRACE + message: + en: True + dir: messages/ + format: json + iverilog: + en: True + subdir: ivcode_nodebug +load: + prompt: + path: config/initial_prompts/prompt1.txt + pick_idx: [] + stage_template: + path: config/templates/stage_template0301.txt +gpt: + model: 4o + key_path: config/key_API.json + temperature: None + json_mode: False + chatgpt: + start_form: chat + one_time_talk: False + rtlgen_model: None +iverilog: + dir: + task_id: +autoline: + result_path: results + cga: + enabled: True + max_iter: 10 + target_coverage: 100.0 + probset: + path: None + mutant_path: None + gptgenRTL_path: None + more_info_paths: [] + only: ['lemmings3', 'lemmings4', 'ece241_2013_q8', '2014_q3fsm', 'm2014_q6', 'review2015_fsm', 'rule110', 'fsm_ps2'] + exclude: [] + exclude_json: None + filter: [{}] + checklist: + max: 3 + debug: + max: 5 + reboot: 1 + py_rollback: 2 + onlyrun: None + promptscript: None + timeout: 300 + TBcheck: + rtl_num: 20 + correct_max: 3 + discrim_mode: col_full_wrong + correct_mode: naive + rtl_compens_en: True + rtl_compens_max_iter: 3 + itermax: 10 + update_desc: False + save_compile: True + save_finalcodes: True + error_interruption: False +------------------------------------------ +2026-03-30 15:50:24 | INFO | [example_mul] [TBgen] stage_0 ends (5.68s used) +2026-03-30 15:50:48 | INFO | [example_mul] [TBgen] stage_1 ends (24.62s used) +2026-03-30 15:51:23 | INFO | [example_mul] [TBgen] stage_2 ends (34.70s used) +2026-03-30 15:51:53 | INFO | [example_mul] [TBgen] stage_3 ends (29.57s used) +2026-03-30 15:53:48 | INFO | [example_mul] [TBgen] stage_4 ends (115.37s used) +2026-03-30 15:53:48 | INFO | [example_mul] [TBgen] stage_checklist ends (0.00s used) +2026-03-30 15:56:14 | INFO | [example_mul] [TBgen] stage_4b ends (146.02s used) +2026-03-30 15:56:31 | INFO | [example_mul] [TBgen] stage_5 ends (16.58s used) +2026-03-30 15:56:31 | INFO | [example_mul] +2026-03-30 15:56:31 | INFO | [example_mul] [TBsim] iverilog compilation : passed! +2026-03-30 15:56:31 | INFO | [example_mul] [TBsim] python simulation : passed! +2026-03-30 15:56:31 | INFO | [example_mul] [TBsim] TBsim finished : True! +2026-03-30 15:56:31 | INFO | [example_mul] +2026-03-30 15:56:31 | INFO | [example_mul] [TBeval] Eval 1: Golden RTL checking begins +2026-03-30 15:56:31 | POSITIVE | [example_mul] [TBeval] Eval 1: Golden RTL checking passed! +2026-03-30 15:56:31 | INFO | [example_mul] diff --git a/saves/0330~0405/20260330_162521/20260330_162521.log b/saves/0330~0405/20260330_162521/20260330_162521.log new file mode 100644 index 0000000..c7a29c4 --- /dev/null +++ b/saves/0330~0405/20260330_162521/20260330_162521.log @@ -0,0 +1,192 @@ + +---------------custom config-------------- +run: + mode: autoline +gpt: + model: qwen-max + key_path: config/key_API.json +save: + en: True + root: ./output/ +autoline: + cga: + enabled: True + max_iter: 10 + promptscript: pychecker + onlyrun: TBgensimeval +------------------------------------------ +------config info (custom + default)------ +run: + version: 2.0 + author: Ruidi Qiu - Technical University of Munich + time: 20260330_162521 + custom_path: /home/zhang/CorrectBench/TBgen_App/config/custom.yaml + mode: autoline + hostname: localhost + pid: 1126823 + pyversion: 3.12.3 (main, Mar 3 2026, 12:15:18) [GCC 13.3.0] +save: + en: True + root: ./output/ + pub: + prefix: None + dir: saves/0330~0405/ + subdir: + log: + en: True + dir: logs/ + notes: None + cfg_pmode: iwantall + debug_en: False + level: TRACE + message: + en: True + dir: messages/ + format: json + iverilog: + en: True + subdir: ivcode_nodebug +load: + prompt: + path: config/initial_prompts/prompt1.txt + pick_idx: [] + stage_template: + path: config/templates/stage_template0301.txt +gpt: + model: qwen-max + key_path: config/key_API.json + temperature: None + json_mode: False + chatgpt: + start_form: chat + one_time_talk: False + rtlgen_model: qwen-max +iverilog: + dir: + task_id: +autoline: + result_path: results + cga: + enabled: True + max_iter: 10 + target_coverage: 100.0 + probset: + path: None + mutant_path: None + gptgenRTL_path: None + more_info_paths: [] + only: ['lemmings3', 'lemmings4', 'ece241_2013_q8', '2014_q3fsm', 'm2014_q6', 'review2015_fsm', 'rule110', 'fsm_ps2'] + exclude: [] + exclude_json: None + filter: [{}] + checklist: + max: 3 + debug: + max: 5 + reboot: 1 + py_rollback: 2 + onlyrun: TBgensimeval + promptscript: pychecker + timeout: 300 + TBcheck: + rtl_num: 20 + correct_max: 3 + discrim_mode: col_full_wrong + correct_mode: naive + rtl_compens_en: True + rtl_compens_max_iter: 3 + itermax: 10 + update_desc: False + save_compile: True + save_finalcodes: True + error_interruption: False +_initialized: True +------------------------------------------ + +--------------default config-------------- +run: + version: 2.0 + author: Ruidi Qiu - Technical University of Munich + time: None + custom_path: None + mode: qwen-max +save: + en: True + root: None + pub: + prefix: None + dir: saves/$weekrange$/ + subdir: + log: + en: True + dir: logs/ + notes: None + cfg_pmode: iwantall + debug_en: False + level: TRACE + message: + en: True + dir: messages/ + format: json + iverilog: + en: True + subdir: ivcode_nodebug +load: + prompt: + path: config/initial_prompts/prompt1.txt + pick_idx: [] + stage_template: + path: config/templates/stage_template0301.txt +gpt: + model: 4o + key_path: config/key_API.json + temperature: None + json_mode: False + chatgpt: + start_form: chat + one_time_talk: False + rtlgen_model: None +iverilog: + dir: + task_id: +autoline: + result_path: results + cga: + enabled: True + max_iter: 10 + target_coverage: 100.0 + probset: + path: None + mutant_path: None + gptgenRTL_path: None + more_info_paths: [] + only: ['lemmings3', 'lemmings4', 'ece241_2013_q8', '2014_q3fsm', 'm2014_q6', 'review2015_fsm', 'rule110', 'fsm_ps2'] + exclude: [] + exclude_json: None + filter: [{}] + checklist: + max: 3 + debug: + max: 5 + reboot: 1 + py_rollback: 2 + onlyrun: None + promptscript: None + timeout: 300 + TBcheck: + rtl_num: 20 + correct_max: 3 + discrim_mode: col_full_wrong + correct_mode: naive + rtl_compens_en: True + rtl_compens_max_iter: 3 + itermax: 10 + update_desc: False + save_compile: True + save_finalcodes: True + error_interruption: False +------------------------------------------ +2026-03-30 16:25:30 | INFO | [example_mul] [TBgen] stage_0 ends (7.27s used) +2026-03-30 16:26:11 | INFO | [example_mul] [TBgen] stage_1 ends (41.38s used) +2026-03-30 16:29:15 | INFO | [example_mul] [TBgen] stage_2 ends (183.67s used) +2026-03-30 16:30:51 | INFO | [example_mul] [TBgen] stage_3 ends (96.39s used) diff --git a/utils/__init__.py b/utils/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/utils/__pycache__/__init__.cpython-312.pyc b/utils/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..92557b32663dd0f2b2738e99afab869a7e8f836d GIT binary patch literal 149 zcmX@j%ge<81m`qPWrFC(AOanHW&w&!XQ*V*Wb|9fP{ah}eFmxdWul*vpPQ;*m64d2 zuJ4>*RFs-r;*^?~oS`4$l%ARw?^sZvUs{rxQ>-5!pP83g5+AQuQ2C3)CO1E&G$+-r Wh!toEBM=vZ7$2D#85xV1fh+)wC?jkD literal 0 HcmV?d00001 diff --git a/utils/__pycache__/subproc.cpython-312.pyc b/utils/__pycache__/subproc.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d53f9ea575945d3f261505801aa14daac4f636e9 GIT binary patch literal 1972 zcmah~O>7%Q6rT02?X{f*l%#b~BQ&@zs;eZ9ohXScQjw4dDk>CcB$Nora(5>7ruObK zGfRH3r5rdUs8X9#df>p3DwPm7kPwJoIY3n;q7^qzxuv8BloM~pYd38XjO4fP&HMl6 z&G_f;?j(Y>aI$1?#1Q&Z3hfmMnd1Rq_7FkKh-f5I)0QK|Ao^$8vc3_?MmN7n&QWeL z+Y`3y;3CF&v1)U?V%I2!FQc`xpacsSmwh1Izu__0q8wDd3DnS6_)8 z42iexnnuI?AB!ktQ-jcPS4=b3FdETDj3mC$ucPzmR4-l6T1x2xVy|ej?w4NF?T1?$ zl3g|;(52aoEumZaHMFiTp*1a=EdK(H;iNoY7z|v-R-Itm!MsXqHC%QGjFRw~gGH5M z*B72I)GlSrWy$|C>=e zLsbE)=i@T(=!xP_EDeA6sC$Zc$435MHwZ z>k#KQ0GSZBOb3Dt+~5i;*X8{_r96d4zJcA1H|M@ zk=25Dh|Oly7c0Y4hw|crn=XxYX=%0*%bG!idqH%;^{5jV3s)EBgOugg>%L=K@O%a_ z0>esZkZhfe;(Hcb<2=9a*^H9FxCw`sO$0ieIx`_x&=nS-rXtY1ARZ2L5Qi-}#X(Bx zT9eD<8^BUPxO`FM2&;86ma78pxy@?XspQ^r8Kah%rH)n2EzVY`Q=0L-oGbALZcp{}0aQ-cpvgMT3d z0-K53v7Oj0^Psn126+|8+4SeBqXZ~VcptRzQ)EV;O`!*=?ChleU_t|$)ehG|lQn{P zsYG0>R0@($|Ls^e=oAe1&@r%xoda@WzEO9HU!zx821Gd_{0xvIUDLG3k*H=I_aJTH NiILIfHTD{4{sHO135oy! literal 0 HcmV?d00001 diff --git a/utils/__pycache__/utils.cpython-312.pyc b/utils/__pycache__/utils.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..0b6b6d6f0e5cd915d242d6047d5cb730f554f90c GIT binary patch literal 11808 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 str: + """ + convert a list of string/number to a string; + + to show the list in the way what we see it in the code + + if string, add '' around it; if number, do nothing + + Example: + :: + + str_list(['a', 2, '3']) -> "['a', 2, '3']" + """ + if len(list) == 0: + return '[]' + str_list = '[' + for i in list: + if isinstance(i, str): + str_list += "'%s', " % (i) + elif isinstance(i, int): # number + str_list += "%d, " % (i) + else: # number + str_list += "%.*f, " % (precision, i) + str_list = str_list[:-2] + ']' + return str_list + + +###################### decorators ###################### +def print_time(en=True): + """ + print the running time of a function + + For example: + :: + + @print_time() + def forward(self, input): + return self.top_level(input) + """ + def decorator_nopara(func): + def wrapper(*args, **kwargs): + if en: + old_time = time.time() + result = func(*args, **kwargs) + func_name = str(func).split(' ')[1] + run_time = time.time() - old_time + print('{} use time: {}s'.format(func_name, run_time)) + else: + result = func(*args, **kwargs) + return result + return wrapper + return decorator_nopara + + +def raise_error(func): + """ + decorator + + raise error after a function + """ + def wrapper(*args, **kwargs): + func(*args, **kwargs) + raise Exception('this error is raised by debug decorator "raise_error"') + return wrapper + +########################################################## +class Timer: + """ + print the running time of a code block + + Args: + + - code_name (str): the name of the code block; default: None + + - print_en (bool): whether to print the running time; default: True + + Example 1 (print time on the console): + :: + + with Timer('test') as t: + loss.backward() # some code + # this will print 'test: time cost = 0.1s' on the console + + Example 2 (get time of a code block): + :: + + with Timer(print_en=False) as t: + loss.backward() # some code + time_cost = t.interval # time_cost = 0.1 + """ + def __init__(self, code_name=None, print_en=True): + self.code_name = code_name + self.print_en = print_en + + def __enter__(self): + self.start = time.time() + return self + + def __exit__(self , *args): + self.end = time.time() + self.interval_time = self.end - self.start + print_line = 'time cost = %.4fs'%(self.interval_time) + if self.code_name is not None: + print_line = self.code_name + ': ' + print_line + if self.print_en: + print(print_line) + self.print_line = print_line + + @property + def interval(self): + return self.interval_time + + @property + def name(self): + return self.code_name + + @property + def info(self): + return self.print_line + + @property + def message(self): + return self.print_line + +def get_time(compact=False): + """ + get the string of current time, format: '%H:%M:%S %Y-%m-%d' + """ + if compact: + return get_time_compact() + else: + return time.strftime('%H:%M:%S %Y-%m-%d', time.localtime(time.time())) + +def get_time_compact(): + now = datetime.now() + time_str = now.strftime("%Y%m%d_%H%M%S") + return time_str + +class run_in_dir: + """ + change the current directory to a new directory, and then change it back after the code block + + Args: + dir (str): the new directory (relative path to the current directory) + """ + def __init__(self, dir): + self.new_dir_relative = dir + def __enter__(self): + self.old_dir = os.getcwd() + self.new_dir = os.path.join(self.old_dir, self.new_dir_relative) + os.chdir(self.new_dir) + def __exit__(self, *args): + os.chdir(self.old_dir) + +################# utils from pytorch ############### +def _ntuple(n, name="parse"): + def parse(x): + if isinstance(x, collections.abc.Iterable): + return tuple(x) + return tuple(repeat(x, n)) + + parse.__name__ = name + return parse + +_single = _ntuple(1, "_single") +_pair = _ntuple(2, "_pair") +_triple = _ntuple(3, "_triple") +_quadruple = _ntuple(4, "_quadruple") + + +################# some tools ################# +def clean_wave_vcd(clean_dir, cnt_en=False): + """ + remove all the "wave.vcd" files in the directory + """ + cnt = 0 + for root, dirs, files in os.walk(clean_dir): + for file in files: + # must be strictly equal to "wave.vcd" + if file == "wave.vcd": + os.remove(os.path.join(root, file)) + if cnt_en: + cnt += 1 + if cnt % 100 == 0: + print("%d files cleaned" % (cnt)) + +def get_week_range(start_day:str|int="Monday", today=None)->str: + """ + - function: + - return the week range of the current week, the start day can be any day of the week + - for example, if today is 20240807, which is wednesday, if the start_day is "Monday", the output will be "0805~0811"; if the start day is "Tuesday", the output will be "0806~0812"; if the start day is "Thursday", the output will be "0801~0807" + - input: + - start_day: the start day of the week, can be a string or an integer + - string: the name of the day, for example, "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday" + - integer: the index of the day, 0 is Monday, 1 is Tuesday, 2 is Wednesday, 3 is Thursday, 4 is Friday, 5 is Saturday, 6 is Sunday, invalid index will be mod 7 + - today: the date of the day, if None, the current date will be used; + - formart: "%Y%m%d", e.g. "20240807" + """ + weekday_map = {"Monday": 0, "Tuesday": 1, "Wednesday": 2, "Thursday": 3, "Friday": 4, "Saturday": 5, "Sunday": 6} + start_day = weekday_map[start_day] if isinstance(start_day, str) else start_day % 7 + # Get the current date + # today = datetime.today() + if today is None: + today = datetime.today() + else: + today = datetime.strptime(today, "%Y%m%d") + + # Calculate the current day of the week (0 is Monday, 6 is Sunday) + current_weekday = today.weekday() + + # Calculate the number of days to subtract to get to the start day + days_to_subtract = (current_weekday - start_day) % 7 + + # Subtract the days to get to the start day + start = today - timedelta(days=days_to_subtract) + + # the output format is like "0805~0811" + end = start + timedelta(days=6) + return start.strftime("%m%d") + "~" + end.strftime("%m%d") + +def run_with_timeout(timeout): + def decorator(func): + @wraps(func) + def wrapper(*args, **kwargs): + # Define a thread target function to run the target code + def target(result): + try: + result.append(func(*args, **kwargs)) + except Exception as e: + result.append(e) + + # List used to store the function result + result = [] + # Create a thread + thread = threading.Thread(target=target, args=(result,)) + # Start the thread + thread.start() + # Wait for the thread to complete, with a specified timeout + thread.join(timeout) + + # If the thread is still alive, it timed out + if thread.is_alive(): + raise TimeoutError(f"Function call timed out after {timeout} seconds") + + # If the thread finished, check if there was an exception + if isinstance(result[0], Exception): + raise result[0] + + # Return the function result + return result[0] + + return wrapper + return decorator \ No newline at end of file diff --git a/utils/verilator_call.py b/utils/verilator_call.py new file mode 100644 index 0000000..e5eb200 --- /dev/null +++ b/utils/verilator_call.py @@ -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 ") +# 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}") \ No newline at end of file