803 lines
31 KiB
Python
803 lines
31 KiB
Python
|
|
"""
|
||
|
|
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)
|