上传所有文件
This commit is contained in:
21
prompt_scripts/__init__.py
Normal file
21
prompt_scripts/__init__.py
Normal file
@@ -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")
|
||||
BIN
prompt_scripts/__pycache__/__init__.cpython-312.pyc
Normal file
BIN
prompt_scripts/__pycache__/__init__.cpython-312.pyc
Normal file
Binary file not shown.
BIN
prompt_scripts/__pycache__/base_script.cpython-312.pyc
Normal file
BIN
prompt_scripts/__pycache__/base_script.cpython-312.pyc
Normal file
Binary file not shown.
BIN
prompt_scripts/__pycache__/script_directgen.cpython-312.pyc
Normal file
BIN
prompt_scripts/__pycache__/script_directgen.cpython-312.pyc
Normal file
Binary file not shown.
BIN
prompt_scripts/__pycache__/script_pychecker.cpython-312.pyc
Normal file
BIN
prompt_scripts/__pycache__/script_pychecker.cpython-312.pyc
Normal file
Binary file not shown.
Binary file not shown.
BIN
prompt_scripts/__pycache__/script_pychecker_SEQ.cpython-312.pyc
Normal file
BIN
prompt_scripts/__pycache__/script_pychecker_SEQ.cpython-312.pyc
Normal file
Binary file not shown.
BIN
prompt_scripts/__pycache__/utils.cpython-312.pyc
Normal file
BIN
prompt_scripts/__pycache__/utils.cpython-312.pyc
Normal file
Binary file not shown.
319
prompt_scripts/base_script.py
Normal file
319
prompt_scripts/base_script.py
Normal file
@@ -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
|
||||
Binary file not shown.
288
prompt_scripts/legacy/script_RTLchecker0306.py
Normal file
288
prompt_scripts/legacy/script_RTLchecker0306.py
Normal file
@@ -0,0 +1,288 @@
|
||||
"""
|
||||
Description : original txt script: config/templates/script_template/DUT_stage_template_0306.txt
|
||||
Author : Ruidi Qiu (r.qiu@tum.de)
|
||||
Time : 2024/3/22 13:02:22
|
||||
LastEdited : 2024/7/24 19:53:29
|
||||
"""
|
||||
|
||||
from ..base_script import BaseScript, BaseScriptStage
|
||||
import json
|
||||
|
||||
class WF_RTLchecker0306(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
|
||||
|
||||
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)
|
||||
# stage3b
|
||||
self.stage3b = Stage3b(self.prob_data, self.stage1.response, self.stage3.response, **self.gptkwargs)
|
||||
self.stage_operation(self.stage3b)
|
||||
# stage4
|
||||
self.stage4 = Stage4(self.prob_data, self.stage1.response, self.stage2.response, self.stage3b.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)
|
||||
# add stage3b's golden DUT to the end of the final TB code
|
||||
# self.TB_code += "\n" + stage3b.response #now in stage 4
|
||||
|
||||
def make_and_run_reboot_stages(self, debug_dir):
|
||||
# stage4
|
||||
stage4_reboot = Stage4(self.prob_data, self.stage1.response, self.stage2.response, self.stage3b.response, **self.gptkwargs)
|
||||
self.stage_operation(stage4_reboot, debug_dir, reboot_en=True)
|
||||
# stagechecklist
|
||||
stagecheck = StageChecklist(self.TB_code, self.stage2.response, self.max_check_iter, **self.gptkwargs)
|
||||
self.stage_operation(stagecheck, debug_dir, reboot_en=True)
|
||||
|
||||
|
||||
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:
|
||||
{
|
||||
"circuit type": "...", # type: string. should be "CMB" for combinational circuit or "SEQ" for sequential circuit. you should only choose one from "CMB" and "SEQ".
|
||||
"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)
|
||||
|
||||
|
||||
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)
|
||||
|
||||
10
prompt_scripts/public_stages.py
Normal file
10
prompt_scripts/public_stages.py
Normal file
@@ -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
|
||||
60
prompt_scripts/script_directgen.py
Normal file
60
prompt_scripts/script_directgen.py
Normal file
@@ -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
|
||||
311
prompt_scripts/script_pychecker.py
Normal file
311
prompt_scripts/script_pychecker.py
Normal file
@@ -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
|
||||
313
prompt_scripts/script_pychecker_CMB_new.py
Normal file
313
prompt_scripts/script_pychecker_CMB_new.py
Normal file
@@ -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
|
||||
527
prompt_scripts/script_pychecker_SEQ.py
Normal file
527
prompt_scripts/script_pychecker_SEQ.py
Normal file
@@ -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()
|
||||
803
prompt_scripts/utils.py
Normal file
803
prompt_scripts/utils.py
Normal file
@@ -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)
|
||||
Reference in New Issue
Block a user