-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathtestbench_generator.py
390 lines (310 loc) · 12.1 KB
/
testbench_generator.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
#!/usr/bin/env python3
# -----------------------------------------------------------------------------
# Name: testbench_generator
# Purpose: A program that generates a testbench template of VHDL file(s)
# passed in argument
# python_version: 3.12
# Author: DOUDOU DIAWARA
#
# Created: 16/02/2024
# License: MIT License
# -----------------------------------------------------------------------------
"""
This module generates a testbench template of a VHDL file(s) passed in Argument(s).
Functions:
parse_file(str):
parse the VHDL file passed in Argument.
component_interface(str)-> str:
Extract the component declaration in the VHDL File(entity declaration).
remove_directional_signals(str) -> str :
Remove directional signals "in" and "out" and "buffer" from the string.
parse_signals(str) -> str:
Get the input and output signal
and generic constant declared in the entity VHDL file.
map_signals(str) -> str:
Map signals declaration to component.
write_testbench(str) -> int:
Write the testbench file of the component interface.
0 if successful otherwise -1
"""
import sys
import os
import re
class bcolors:
""" colors for the terminal """
HEADER = '\033[95m'
OKBLUE = '\033[94m'
OKCYAN = '\033[96m'
OKGREEN = '\033[92m'
WARNING = '\033[93m'
FAIL = '\033[91m'
ENDC = '\033[0m'
BOLD = '\033[1m'
UNDERLINE = '\033[4m'
def parse_file(file_name: str):
"""
Parse the VHDL file to look for the entity declaration in the file.
Args:
file_name (str): the path to the VHDL file to parse.
If the VHDL file doesn't exit, the function will terminate.
Returns:
(str): the entity declaration of the VHDL file.
0 if the file was not found
-1 if a error occurred when processing the file
"""
found_entity = False
entity_lines = []
try:
with open(file_name, 'r', encoding="utf-8") as file:
# reading file line by line
for line in file:
line = line.lower().strip()
# line of the entity declaration
if line.startswith("entity"):
found_entity = True
if found_entity:
entity_lines.append(line)
if found_entity and line.startswith("end"):
break
if not found_entity:
print(bcolors.WARNING + f"""not able to find the entity declaration in your VHDL file!
end of processing: {file_name} """)
return 0
# return the entity declaration in the file
return "\n".join(entity_lines)
except FileNotFoundError as e:
print(bcolors.WARNING + "Unable to open the file " + file_name + ": " + str(e) + bcolors.ENDC)
except PermissionError:
print(bcolors.WARNING + "Permission denied! Unable to read the file." + bcolors.ENDC)
except Exception as e:
print(bcolors.FAIL + f"An error occurred: {e}" + bcolors.ENDC)
return -1
def component_interface(entity_lines: str) -> str:
"""
Return the component of the entity declaration.
Args:
entity_lines(str): entity declaration.
Returns:
(str): the component to map
"""
# replace entity by component
entity_lines_component = entity_lines.replace("entity", "component")
# to check if component end with "end <entity_name>" or "end component <entity_name>"
if "end component" not in entity_lines_component:
entity_lines_component = entity_lines_component.replace("end", "end component")
return entity_lines_component
def remove_directional_signals(signals: str) -> str:
"""
Removes directional signals "in" and "out" and "buffer" from the signals.
Args:
signals(str): the component interface.
Returns:
str: The content with directional signals removed.
"""
words = signals.split()
filtered_lines = []
for word in words:
if word.lower() not in ("in", "out", "buffer"):
filtered_lines.append(word)
return " ".join(filtered_lines)
def parse_signals(component: str) -> str:
"""
Get the input and output signals and generic constants declared in the entity VHDL file.
Args:
component (str): The component declaration content
Returns:
(str) of the VHDL signals and generic constant to define in the testbench file.
"""
semicolon_newline = ";\n"
# add variable type signal
data_type_signal = []
# extract generic constant declaration if exists
if "generic" in component:
temp_before = component.find("(") + 1
temp_after = component.find(");", temp_before)
generic_const = component[temp_before:temp_after]
# if no default value is given for the generic constant
find = re.search(r"\d+", generic_const)
if find is None:
# add default value
generic_const = generic_const + " := 8"
# declaration const generic
declaration_generic_const = " constant " + generic_const + semicolon_newline
data_type_signal.append(declaration_generic_const)
# extract input,output signals
# find port substring start
port_substring = component.find("port") + len("port")
if port_substring >= 0:
first_parenthese = component.find("(", port_substring) + 1
last_parenthese = component.rfind(");", first_parenthese)
signal_declaration = component[first_parenthese:last_parenthese]
# removing in/out/buffer directional signal
signals = remove_directional_signals(signal_declaration)
list_signals = signals.split(";")
for signal_name in list_signals:
data_type_signal.append("signal " + signal_name + semicolon_newline)
return " ".join(data_type_signal)
return "port declaration not found in file!"
def map_signals(signals: str) -> str:
"""
map signals declaration to component
Args:
signals(str): signals declared in the testbench file
Returns:
mapped_signals(str): signals mapped to the component to test
"""
indentation = "\t\t\t\t"
# signals to map to the component to test
signals_to_map = "generic map ( "
tmp_signals = signals.splitlines()
# name of signal identifier
signal_name = ""
# check if there's a generic constant declaration
generic = False
# generic map first
for signal in tmp_signals:
# remove data type declaration
signal = signal[: signal.find(":")]
if "constant" in signal:
generic = True
signal_name = signal[len("constant") + 1:].strip()
signal_name = signal_name + "=>" + signal_name + ",\n"
signals_to_map += signal_name
if generic:
# remove last comma character
signals_to_map = signals_to_map[:signals_to_map.rfind(",")]
# end generic mapping
signals_to_map = signals_to_map + ")\n"
else:
# no generic to map
signals_to_map = ""
# no indention
indentation = ""
# signal map port
signals_to_map += indentation + "port map ( "
for signal in tmp_signals:
# remove data type declaration
signal = signal[: signal.find(":")]
# remove variable type (signal|variable|constant)
if "constant" in signal:
continue
if "signal" in signal:
signal_name = signal[len("signal") + 1:].strip()
elif "variable" in signal:
signal_name = signal[len("variable") + 1:].strip()
# multiples lines variables declaration
for name in signal_name.split(","):
if len(name) > 0:
# append to signal to map
signals_to_map += name + "=>" + name + ",\n\t\t\t\t"
# remove last comma character
signals_to_map = signals_to_map[:signals_to_map.rfind(",")]
# end signals mapping
signals_to_map = signals_to_map + ");\n"
return signals_to_map
def write_testbench(entity_lines: str) -> int:
"""
Write the testbench file of the component interface.
Args:
component (str): The component interface declaration.
Returns:
int: 0 if successful, -1 otherwise.
"""
# list of the different info of the component
info_interface = (component_interface(entity_lines)).split("\n")
# entity name
before_entity = (info_interface[0].find("component") + len("component")) + 1
after_entity = info_interface[0].find("is")
# extract the entity name of the VHDL file
entity_name = info_interface[0][before_entity:after_entity].strip()
# file name
testbench_file_name = entity_name + "_tb.vhdl".capitalize()
# file path
file_path = os.path.join(os.getcwd(), testbench_file_name)
# component to test
component_test = '\n'.join(info_interface).strip()
# parse signals declaration in the VHDL entity
signals = parse_signals(component_test)
# signals to map
mapping_signals = map_signals(signals)
# testbench
testbemch_template = f"""
library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
entity {entity_name.capitalize()}_tb is
end entity {entity_name.capitalize()}_tb;
architecture Behavior of {entity_name}_tb is
-- component to test
{component_test}
-- signal to map to component
{signals}
begin
-- map signals
uut: {entity_name} {mapping_signals}
stimulus: process is
begin
-- write your test here
wait;
end process;
end architecture behaviour;
"""
# check if that file_name already exists
override = False
if os.path.exists(file_path):
override = True
answer = input(bcolors.WARNING + "file " + testbench_file_name + " already exists do you want to override it?([Yes/Y/y or No/N/n]): " + bcolors.ENDC).lower()
# we only need to check the value of answer when override is True
if not (override) or answer in ("yes", "y"):
# write the testbench file
try:
with open(testbench_file_name, 'w', encoding="utf-8") as writer:
writer.write(testbemch_template)
print(bcolors.OKGREEN + "GENERATED TESTBENCH! : " + file_path + bcolors.ENDC)
except Exception as e:
print(bcolors.FAIL + f"An error occurred: {e}" + bcolors.ENDC)
return -1
return 0
def main():
"""
Main start script
"""
# help command
help_usage = """
Usage: python3 testbench_generator.py [VHDL FILE]...
Generate testbench template of VHDL FILE(s) to current working directory
Examples:
python3 testbench_generator.py adder.vhdl mux.vhdl ...
"""
if len(sys.argv) < 2:
print(help_usage)
else:
# processing every arguments (except first argument(script name))
for arg in sys.argv[1:]:
# check if VHDL file
if arg.endswith((".vhdl", ".vhd")):
# processing arguments
entity_lines = parse_file(arg)
# error if parse_file return a int value
if isinstance(entity_lines, int):
return entity_lines
# write the testbench file
write_testbench(entity_lines)
else:
# verify with the user if VHDL file are passed in argument
valid = False
while not valid:
answer = input(bcolors.WARNING + "Are the file(s) passed as argument VHDL file(s)? ([Yes/Y/y or No/N/n]): " + bcolors.ENDC).lower()
if answer in ("yes", "y"):
valid = True
# process files arguments
parse_file(arg)
elif answer in ("no", "n"):
# quit
print("Terminated!")
valid = True
else:
print(bcolors.WARNING + "Please answer by (Yes/Y/y) if argument(s) are VHDL file(s) otherwise answer by (No/N/n)" + + bcolors.ENDC)
return 0
if __name__ == '__main__':
main()