上传所有文件

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

View 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")

Binary file not shown.

Binary file not shown.

Binary file not shown.

View 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

View 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)

View 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

View 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

View 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

View 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

View 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
View 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)