311 lines
17 KiB
Python
311 lines
17 KiB
Python
|
|
"""
|
||
|
|
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
|