pluto_hdl_adi/.github/scripts/check_guideline.py

1142 lines
45 KiB
Python
Executable File

#!/usr/bin/env python3
##############################################################################
## Copyright (C) 2022-2023 Analog Devices, Inc. All rights reserved.
### SPDX short identifier: ADIBSD
#
## Check readme_check_guideline.md from the same folder, for more details.
##############################################################################
import os
import re
import codecs
import sys
from datetime import datetime
##############################################################################
#
# Class definitions
##############################################################################
class Port (object):
def __init__ (self, name="unknown", direction="unknown", ptype="wire"):
self.name = name
self.direction = direction
self.ptype = ptype
class Occurrence (object):
# path - to the file where the occurrence was found
# line - where the instantiated module is
# line_end - where the instantiated module ends
# pos_start_ports - how many lines after .line the ports list starts, inside
# the instantiated module
def __init__ (self, path="unknown", line="unknown"):
self.path = path
self.line = line
self.line_end = -1
self.pos_start_ports = -1
class Interface (object):
def __init__ (self):
self.interface = []
def add_port (self, port):
self.interface.append(port)
##############################################################################
#
# Functions
##############################################################################
def is_comment (line):
rcoma = re.compile(r'^\s*//')
rcomb = re.compile(r'^\s*/\*')
if (rcoma.match(line) or rcomb.match(line)):
return True
else:
return False
def is_multiline_comment (line):
if ((line.strip()).startswith("*")):
return True
else:
if ((line.find("/*") != -1) or (line.find("*/") != -1)):
return True
else:
return False
def is_paramdef (line):
rparameter = re.compile(r'^\s*parameter\s.*')
if (rparameter.match(line)):
return True
else:
return False
def is_iodef (line):
rinput = re.compile(r'^\s*input\s.*')
routput= re.compile(r'^\s*output\s.*')
rinout = re.compile(r'^\s*inout\s.*')
if ((rinput.match(line)) or (routput.match(line)) or (rinout.match(line))):
return True
else:
return False
# check if the given string is made only of spaces or tabs
def only_spaces_or_tabs (substr):
substr = substr.strip()
substr = substr.strip("\t")
if (substr == ""):
return True
else:
return False
# check if one of the modified files appears in the warning message
def list_has_substring (modified_files, message):
for mfile in modified_files:
if (message.find(mfile) != -1):
return True
return False
# check if file is between the modified files specified as arguments
def string_in_list (module_path, modified_files):
for mfile_path in modified_files:
if (("./" + mfile_path) == module_path or mfile_path == module_path):
return True
return False
###############################################################################
#
# Check if file has correct properties, meaning that the file extension has to
# be .v and it should not be some certain files (.sv, tb)
# Returns true or false.
###############################################################################
def check_filename (filename):
if (filename.endswith('.v') == False):
return False
if (filename.endswith('.sv') == True):
return False
if (filename.find("tb") != -1):
return False
return True
###############################################################################
#
# Detect all modules present in the given directory in /library and /projects.
# Return a list with the relative paths.
###############################################################################
def detect_all_modules (directory):
detected_modules_list = []
for folder, dirs, files in os.walk(directory):
## folder name must be either library or projects,
## and it must not contain a dot in the name (Vivado generated)
if ((folder[1:-2]).find(".") == -1
and (folder.find("library") != -1 or folder.find("projects") != -1)):
for file in files:
#filename_wout_ext = (os.path.splitext(file)[0])
if (check_filename(file)):
fullpath = os.path.join(folder, file)
detected_modules_list.append(fullpath)
return detected_modules_list
###############################################################################
#
# Determine the file name from the fullpath.
# Return the string containing the file name without extension.
###############################################################################
def get_file_name (module_path):
# split the path using the / and take the last group, which is the file.ext
split_path = module_path.split("/")
module_filename = split_path[len(split_path) - 1]
# take the module name from the filename with the extension
filename_wout_ext = module_filename.split(".")[0]
return filename_wout_ext
###############################################################################
#
# Check if there are lines after `endmodule and two consecutive empty lines,
# and if there are and edit_files is true, delete them.
###############################################################################
def check_extra_lines (module_path, list_of_lines, lw, edit_files):
passed_endmodule = False
line_nb = 1
prev_line = ""
remove_end_lines = False
if (edit_files):
remove_extra_lines = False
for line in list_of_lines:
# GC: check for lines after endmodule
if (line.find("endmodule") != -1):
passed_endmodule = True
# if we passed the endmodule tag
if (passed_endmodule and (line.find("endmodule") == -1)):
remove_end_lines = True
# GC: check for empty lines
if (line_nb >= 2):
if (only_spaces_or_tabs(prev_line) and only_spaces_or_tabs(line)
and (not is_comment(prev_line)) and (not is_comment(line))):
lw.append(module_path + " : " + str(line_nb) + " two or more consecutive empty lines")
if (edit_files):
remove_extra_lines = True
line_nb += 1
if (line_nb >= 2):
prev_line = line
if (remove_end_lines):
if (edit_files):
deleted_lines = False
passed_endmodule = False
line_nb = 1
while (line_nb <= len(list_of_lines)):
line = list_of_lines[line_nb-1]
if (line.find("endmodule") != -1):
passed_endmodule = True
if (not (passed_endmodule and (line.find("endmodule") == -1))):
line_nb += 1
else:
deleted_lines = True
list_of_lines.pop(line_nb-1)
if (deleted_lines):
lw.append(module_path + " : deleted lines after endmodule")
else:
lw.append(module_path + " : couldn't delete lines after endmodule but must!")
else:
lw.append(module_path + " : extra lines after endmodule")
if (edit_files and remove_extra_lines):
line_nb = 1
prev_line = ""
while (line_nb <= len(list_of_lines)):
line = list_of_lines[line_nb-1]
if (line_nb >= 2):
if (only_spaces_or_tabs(prev_line) and only_spaces_or_tabs(line)
and (not is_comment(prev_line)) and (not is_comment(line))):
lw.append(module_path + " : " + str(line_nb) + " removed consecutive empty lines")
list_of_lines.pop(line_nb-1)
else:
line_nb += 1
else:
line_nb += 1
if (line_nb >= 2):
prev_line = line
###############################################################################
# Get the nth digit from a number.
# The numbering in this scheme uses zero-indexing and starts from the right side
# of the number.
# The // performs integer division by a power of ten to move the digit to the
# ones position, then the % gets the remainder after division by 10.
###############################################################################
def get_digit (number, n):
return number // 10**n % 10
###############################################################################
#
# List of files that strings that the module path must not contain, in order to
# check for the license header.
###############################################################################
avoid_list = []
#avoid_list.append("jesd")
avoid_list.append("fir_interp")
avoid_list.append("cic_interp")
def header_check_allowed (module_path):
for str in avoid_list:
if (module_path.find(str) != -1):
return False
return True
###############################################################################
#
# Check if the license header is written correctly, meaning:
# To have either a range of years from the first time it was committed and
# until the current year
# or just the current year, if this is the first commit.
###############################################################################
def check_copyright (list_of_lines, lw, edit_files):
currentYear = datetime.now().year
# license_header = """// ***************************************************************************
#// ***************************************************************************
#// Copyright (C) """ + str(currentYear) + """ Analog Devices, Inc. All rights reserved.
#//
#// In this HDL repository, there are many different and unique modules, consisting
#// of various HDL (Verilog or VHDL) components. The individual modules are
#// developed independently, and may be accompanied by separate and unique license
#// terms.
#//
#// The user should read each of these license terms, and understand the
#// freedoms and responsibilities that he or she has by using this source/core.
#//
#// This core is distributed in the hope that it will be useful, but WITHOUT ANY
#// WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
#// A PARTICULAR PURPOSE.
#//
#// Redistribution and use of source or resulting binaries, with or without modification
#// of this file, are permitted under one of the following two license terms:
#//
#// 1. The GNU General Public License version 2 as published by the
#// Free Software Foundation, which can be found in the top level directory
#// of this repository (LICENSE_GPL2), and also online at:
#// <https://www.gnu.org/licenses/old-licenses/gpl-2.0.html>
#//
#// OR
#//
#// 2. An ADI specific BSD license, which can be found in the top level directory
#// of this repository (LICENSE_ADIBSD), and also on-line at:
#// https://github.com/analogdevicesinc/hdl/blob/main/LICENSE_ADIBSD
#// This will allow to generate bit files and not release the source code,
#// as long as it attaches to an ADI device.
#//
#// ***************************************************************************
#// ***************************************************************************"""
changed = False
template_matches = True
header_status = -1
# for further development, if the entire license should be checked
# number of lines for the license header text, including the last line
#lh_nb = license_header.count('\n') + 1
# if this is the line with the Copyright year
line_nb = 2
## from [17-25] is the range of years
## [17-20] is the beginning year
## [21] is the dash [-]
## [22-25] is the last year
aux = list(list_of_lines[line_nb])
# match a year range
match = re.match(r'.*(Copyright\s\(C\)\s20[0-9]{2}[-]20[0-9]{2})', list_of_lines[line_nb])
if (match is not None):
# only the last year must be updated (chars [22-25])
c1 = str(get_digit(currentYear, 3))
c2 = str(get_digit(currentYear, 2))
c3 = str(get_digit(currentYear, 1))
c4 = str(get_digit(currentYear, 0))
# if already set to current year, then no edits and no warnings
if (aux[22] == c1 and aux[23] == c2 and aux[24] == c3 and aux[25] == c4):
changed = False
else:
aux[22] = c1
aux[23] = c2
aux[24] = c3
aux[25] = c4
#list_of_lines[line_nb] = "// Copyright (C) " + list_of_lines[line_nb][17] + list_of_lines[line_nb][18] + list_of_lines[line_nb][19] + list_of_lines[line_nb][20] + "-" + str(currentYear) + " Analog Devices, Inc. All rights reserved.\n"
changed = True
else:
# match a single year
match = re.match(r'.*(Copyright\s\(C\)\s20[0-9]{2})', list_of_lines[line_nb])
if (match is not None):
## if the year is different than the currentYear,
## then must make a year range [17-25]
year = aux[17] + aux[18] + aux[19] + aux[20]
if (year != str(currentYear)):
aux.insert(21, '-')
aux.insert(22, str(get_digit(currentYear, 3)))
aux.insert(23, str(get_digit(currentYear, 2)))
aux.insert(24, str(get_digit(currentYear, 1)))
aux.insert(25, str(get_digit(currentYear, 0)))
#list_of_lines[line_nb] = "// Copyright (C) " + str(year) + "-" + str(currentYear) + " Analog Devices, Inc. All rights reserved.\n"
changed = True
else:
changed = False
else:
# if none of the Copyright templates match
template_matches = False
# files can be changed and header got updated
if (edit_files and changed and template_matches):
list_of_lines[line_nb] = "".join(aux)
lw.append(module_path + " : license header updated by the script")
header_status = 1
# header up-to-date already and matches a template
if (not changed and template_matches):
header_status = 2
# files not to be edited and header is not up-to-date
if (not edit_files and changed and template_matches):
lw.append(module_path + " : license header cannot be updated")
header_status = 3
# template doesn't match
if (not template_matches):
lw.append(module_path + " : copyright template doesn't match")
header_status = 4
return header_status
###############################################################################
#
# Check for guideline rules applied to module definitions and the entire file,
# except for the module instances. They are processed in check_guideline_instances.
# This can modify the files if edit_files is true.
# Return the string containing the module name and print errors for guideline
# if it is not respected.
###############################################################################
def get_and_check_module (module_path, lw, edit_files):
lw_initial_size = len(lw)
lw.append("\nAt module definition:")
fp = open("%s" % (module_path), "r")
list_of_lines = fp.readlines()
fp.close()
## do not check the license status for the files that must be avoided,
## since it doesn't apply
if (header_check_allowed(module_path)):
header_status = check_copyright(list_of_lines, lw, edit_files)
# GC: check if the license header is updated
if (header_status == -1):
edited = False
lw.append(module_path + " : copyright text doesn't match the pattern for the Copyright year")
else:
header_status = -1
module_name = ""
name_found = False
params_exist = False
end_line = -1
line_nb = 1
passed_module = False
passed_endmodule = False
last_iodef_line = -1
last_paramdef_line = -1
changed_line1 = -1
changed_line2 = -1
changed_line1_sit = -1
extra_chars = False
for line in list_of_lines:
pos_module = line.find("module")
pos_endmodule = line.find("endmodule")
pos_paranth1 = line.find("(")
pos_comma = line.find(",")
if (pos_module == 0):
passed_module = True
if (pos_endmodule != -1):
passed_endmodule = True
# GC: check for spaces at the end of line
if (re.search(" +$", line) != None):
extra_chars = True
lw.append(module_path + " : " + str(line_nb) + " extra spaces at the end of line")
# if the module declaration didn't end already
if (is_paramdef(line) and passed_module and end_line == -1):
if (pos_comma == -1):
last_paramdef_line = line_nb
else:
pos_comment = line.find("/")
## if the first found comma is after a /, it means it's the
## last parameter line
if (pos_comment > 0 and pos_comment < pos_comma):
last_paramdef_line = line_nb
# if the module declaration didn't end already
if (is_iodef(line) and passed_module and end_line == -1):
if (pos_comma == -1):
last_iodef_line = line_nb
else:
pos_comment = line.find("/")
## if the first found comma is after a /, it means it's the
## last io line
if (pos_comment > 0 and pos_comment < pos_comma):
last_iodef_line = line_nb
# if still inside the module declaration (with params)
if (name_found and params_exist and end_line == -1):
pos_paranth2 = line.find(")")
if (0 <= pos_paranth2 and pos_paranth2 < pos_paranth1):
if (re.search("\)\\s\(", line) != None):
rest_of_line = line.strip().strip("(").strip().strip(")")
## GC: situations when the guideline is not respected:
## 1. | ) (
## 2. |) ( something
## 3. | smth ) ( something
## 4. means it's one of the above
if (pos_paranth2 > 0 or rest_of_line != ""):
changed_line1_sit = 4
lw.append(module_path + " : " + str(line_nb) + " at ) ( not at the beginning of an empty line")
if (edit_files):
# situation 1: clear before ) (
if (pos_paranth2 > 0 and rest_of_line == ""):
changed_line1_sit = 1
aux = list(list_of_lines[line_nb-1])
auxf = [")", " ", "("]
l = 0
for c in aux:
# remove the ), the space and the (
if (l != pos_paranth2 and (l != pos_paranth2 + 1) and l != pos_paranth1):
auxf.append(c)
l += 1
list_of_lines[line_nb-1] = "".join(auxf)
changed_line1 = line_nb
# situation 2: add a newline
if (pos_paranth2 == 0 and rest_of_line != ""):
changed_line1_sit = 2
aux = list(list_of_lines[line_nb-1])
auxf = []
l = 0
for c in aux:
# remove the ), the space and the (
if (l != pos_paranth2 and (l != pos_paranth2 + 1) and l != pos_paranth1):
auxf.append(c)
l += 1
list_of_lines[line_nb-1] = "".join(auxf)
changed_line1 = line_nb
# situation 3: clear before ) ( and add a newline
if (pos_paranth2 > 0 and rest_of_line != ""):
changed_line1_sit = 3
aux = list(list_of_lines[line_nb-1])
auxf = []
l = 0
for c in aux:
# remove the ), the space and the (
if (l != pos_paranth2 and (l != pos_paranth2 + 1) and l != pos_paranth1):
auxf.append(c)
l += 1
list_of_lines[line_nb-1] = "".join(auxf)
changed_line1 = line_nb
else:
lw.append(module_path + " : " + str(line_nb) + " at ) ( has to have exactly 1 space between")
# if still inside the module declaration and regardless of params
if (name_found and end_line == -1):
pos_closing = line.find(");")
if (pos_closing >= 0):
end_line = line_nb
if ((last_iodef_line + 1 != line_nb) or (pos_closing >= 0)):
rest_of_line = line.strip().strip(";").strip().strip(")")
if (pos_closing > 0 or rest_of_line != ""):
lw.append(module_path + " : " + str(line_nb) + " at ); not at the beginning of the next line after the last port")
if (edit_files):
if (pos_closing > 0 or rest_of_line != ""):
aux = list(list_of_lines[line_nb-1])
auxf = []
l = 0
for c in aux:
# remove the ) and ;
if (l != pos_closing and (l != pos_closing + 1)):
auxf.append(c)
l += 1
list_of_lines[line_nb-1] = "".join(auxf)
changed_line2 = line_nb
# GC: check for indentation of the file
## if it's a regular line
if ((pos_module == -1) and (pos_endmodule == -1)
and (not only_spaces_or_tabs(line))
and (not is_comment(line)) and (not is_multiline_comment(line))
and passed_module and (not passed_endmodule)
and (line.find("`") == -1)):
indent_nb = len(line) - len(line.lstrip())
if (not (indent_nb >= 2)):
if (line_nb != (last_paramdef_line+1) and line_nb != (last_iodef_line+1)):
lw.append(module_path + " : " + str(line_nb) + " no indentation found")
else:
# take only iodef from modules and not from functions also
if (indent_nb != 2 and is_paramdef(line)):
lw.append(module_path + " : " + str(line_nb) + " indentation is not proper")
# get the module name by reading the line that contains "module"
# GC: check for proper positioning of the module declaration
if ((not is_comment(line)) and (not name_found)):
if (pos_module == 0):
## situations accepted
## 1. module module_name (
## 2. module module_name #(
pos_diez = line.find("#")
# 2nd situation
if (pos_diez > 0):
if (pos_paranth1 == pos_diez + 1):
module_name = re.search("module(.*?)#\(", line)
if (module_name != None):
module_name = module_name.group(1)
module_name = module_name.strip()
name_found = True
else:
lw.append(module_path + " : " + str(line_nb) + " at module name - error")
else:
lw.append(module_path + " : " + str(line_nb) + " at module #( guideline not respected")
params_exist = True
# 1st situation
else:
module_name = line.strip("module")
module_name = module_name.strip()
module_name = module_name.strip("\n")
module_name = module_name.strip()
module_name = module_name.strip("(")
module_name = module_name.strip()
name_found = True
line_nb += 1
if (edit_files):
if (changed_line1 != -1):
if (changed_line1_sit == 2 or changed_line1_sit == 3):
list_of_lines.insert(changed_line1, ") (\n")
if (changed_line2 != -1):
if (changed_line1 != -1 and changed_line1_sit > 1):
changed_line2 += 1
last_iodef_line += 1
## +1 -1 because we want on the next line after the last iodef line,
## but also the counting with line_nb starts from 1, and in
## files it starts from 0
list_of_lines.insert((last_iodef_line + 1) - 1, ");\n")
# GC: check for lines after endmodule and empty lines
# (and delete them, if desired)
prev_length = len(list_of_lines)
check_extra_lines (module_path, list_of_lines, lw, edit_files)
if (edit_files):
# if at least one of the things was edited
if (changed_line1 != -1 or changed_line2 != -1 or extra_chars
or prev_length != len(list_of_lines) or (header_status == 1)):
# then rewrite the file
with open(module_path, "w") as f:
for line in list_of_lines:
# GC: check for whitespace at the end of the line w\o \n
aux_line = line[:-1]
aux_line = aux_line.rstrip()
f.write(aux_line + "\n")
if (extra_chars):
lw.append(module_path + " : removed extra spaces at the end of lines")
if (not name_found):
lw.append(module_path + " : module name couldn't be extracted\n")
lw_last_size = len(lw)
if (lw_last_size == lw_initial_size + 1):
lw.pop()
return module_name
###############################################################################
#
# Find all occurrences of the given module (path) in all files from the given
# directory (recursively, but only in \library or \projects) or in all files
# from list_of_files (if specified).
# Return list of paths (for the occurrences) relative to the given directory.
###############################################################################
def find_occurrences (directory, module_name, list_of_files):
occurrences_list = []
for folder, dirs, files in os.walk(directory):
## only folder paths without a dot
## and to be either from /library or from /projects
if (not ((folder[1:-2]).find(".") == -1
and (folder.find("library") != -1 or folder.find("projects") != -1))):
continue
for file in files:
fullpath = os.path.join(folder, file)
if (not check_filename(fullpath)):
continue
search = False
if (list_of_files and (string_in_list(fullpath, list_of_files))):
search = True
elif (not list_of_files):
search = True
## the file with the module definition is not accepted and
## neither the files that have to be avoided
if (search and file != (module_name + ".v")):
with codecs.open(fullpath, 'r', encoding='utf-8', errors='ignore') as f:
line_nb = 1
for line in f:
if ((line.find(module_name) != -1) and (not is_comment(line))):
pos = line.find(module_name)
pos_dot = line.find(".")
# if there is no dot before the module name
if (pos_dot == -1 or pos < pos_dot):
if ((line[pos+len(module_name)] == ' ') or (line[pos+len(module_name)] == '#')
or (line[pos+len(module_name)] == '(') or (line[pos+len(module_name)] == '\t')):
# if before the instance name there are only spaces, then it is ok
if (only_spaces_or_tabs(line[:pos-1]) == True):
new_occurrence = Occurrence(path=fullpath, line=line_nb)
## check if it has a parameters list;
## then instance name is on the same line
if ("#" not in line):
new_occurrence.pos_start_ports = 0
occurrences_list.append(new_occurrence)
line_nb += 1
return occurrences_list
###############################################################################
#
# Find the lines where an occurrence starts, ends and where its list of ports
# starts.
# Return nothing (the occurrence_item fields are directly modified)
###############################################################################
def set_occurrence_lines (occurrence_item, list_of_lines):
pos_start_module = -1
pos_end_module = -1
param_exist = False
instance_lines = []
line_nb = 1
# find the start and the end line of the module instance
for line in list_of_lines:
if (pos_end_module == -1):
if (occurrence_item.line == line_nb):
pos_start_module = line_nb
if ("#" in line):
param_exist = True
# if we are inside of the module instance
if (pos_start_module != -1):
if (line.find(");") != -1):
pos_end_module = line_nb
occurrence_item.line_end = pos_end_module
else:
break
line_nb += 1
if (not param_exist):
occurrence_item.pos_start_ports = 0
else:
# with parameters: get the ports' list in all_inst_lines, including parameters
all_inst_lines = ""
line_nb = 1
for line in list_of_lines:
if (pos_start_module <= line_nb and line_nb <= pos_end_module):
all_inst_lines = all_inst_lines + line
elif (line_nb > pos_end_module):
break
line_nb += 1
## find the line where the instance name is;
## the ports should start from the next line, which is pos_start_ports+1
# find a string that is spread over multiple lines
aux_instance_name = re.findall('\)\n(.*?)\(', all_inst_lines, re.M)
# if )\n i_... (
if (len(aux_instance_name) > 0):
instance_name = aux_instance_name[0].strip(" ")
else:
# if ) i_... (
instance_name = re.findall('\)(.*?)\(', all_inst_lines, re.M)[0].strip(" ")
line_nb = 1
pos_start_ports = -1
# update occurrence_item.pos_start_ports if it wasn't already set
for line in list_of_lines:
if (pos_start_module <= line_nb and line_nb <= pos_end_module):
if ((instance_name in line) and (pos_start_ports == -1)):
# if not already specified in find_occurrences, without a parameters list
if (occurrence_item.pos_start_ports == -1):
pos_start_ports = line_nb - pos_start_module
occurrence_item.pos_start_ports = pos_start_ports
elif (line_nb > pos_end_module):
break
line_nb += 1
###############################################################################
#
# Check for the guideline rules applied to the module instaces and output
# warnings for each line, if any.
###############################################################################
def check_guideline_instances (occurrence_item, lw):
# list of warnings
lw_initial_size = len(lw)
lw.append("\nAt instances:")
with open(occurrence_item.path, 'r') as in_file:
list_of_lines = in_file.readlines()
# have all the fields of the occurrence_item
set_occurrence_lines(occurrence_item, list_of_lines)
## with parameters: get the module instance's lines in all_inst_lines,
## including the parameters
all_inst_lines = ""
line_nb = 1
for line in list_of_lines:
if (occurrence_item.line <= line_nb and line_nb <= occurrence_item.line_end):
all_inst_lines = all_inst_lines + line
elif (line_nb > occurrence_item.line_end):
break
line_nb += 1
port_pos = 0
line_nb = 1
spaces_nb = -1
passed_module = False
passed_endmodule = False
for line in list_of_lines:
inside_module_instance = False
line_start_ports = occurrence_item.line + occurrence_item.pos_start_ports
if ((occurrence_item.line <= line_nb) and (line_nb <= occurrence_item.line_end)):
inside_module_instance = True
# GC: indentation for the line where the instance name is
if (line_start_ports == line_nb):
spaces_nb = len(line) - len(line.lstrip())
if ((spaces_nb <= 0) or (spaces_nb % 2 != 0)):
lw.append(occurrence_item.path + " : " + str(line_nb) + " wrong indentation at instance name")
# GC: indentation for the line where the module name is
if (occurrence_item.line == line_nb):
start_spaces_nb = len(line) - len(line.lstrip())
if ((start_spaces_nb <= 0) or (start_spaces_nb % 2 != 0)):
lw.append(occurrence_item.path + " : " + str(line_nb) + " wrong indentation at module name")
# GC: check for proper positioning of the module instance
if (inside_module_instance):
if ("#" in line):
diez_ok = False
if ("." in line):
lw.append(occurrence_item.path + " : " + str(line_nb) + " #(. in module instance")
else:
pos_diez = line.find("#")
pos_paranth1 = line.find("(")
pos_paranth2 = line.find(")")
if ((0 < pos_diez) and (pos_diez + 1 == pos_paranth1) and (pos_paranth2 == -1)):
diez_ok = True
else:
if (pos_paranth2 != -1):
lw.append(occurrence_item.path + " : " + str(line_nb) + " parameters must be each on its own line")
else:
lw.append(occurrence_item.path + " : " + str(line_nb) + " parameters list is not written ok")
# for the line where the instance name is
# find a string like )\n ... (
aux_instance_name = re.findall('\)\n(.*?)\(', all_inst_lines, re.M)
instance_name = ""
# if )\n i_... (
if (len(aux_instance_name) > 0):
instance_name = aux_instance_name[0].strip(" ")
if (")" not in instance_name):
lw.append(occurrence_item.path + " : " + str(line_start_ports) + " ) i_... ( instance name not written ok")
else:
try:
# if ) i_... (
instance_name = re.findall('\)(.*?)\(', all_inst_lines, re.M)[0].strip(" ")
except Exception:
lw.append(occurrence_item.path + " : " + str(occurrence_item.line + occurrence_item.pos_start_ports) + " couldn't extract instance name")
pos_dot = line.find(".")
pos_comma = line.find(",")
pos_closing = line.find(");")
# GC: all ); of instances cannot be on an empty line
aux_line = line.strip()
aux_line = aux_line.strip("\t")
aux_line = aux_line.strip(")")
aux_line = aux_line.strip(";")
if ((pos_closing != -1) and (only_spaces_or_tabs(aux_line))):
lw.append(occurrence_item.path + " : " + str(line_nb) + " ); when closing module instance")
# every dot starting from (.line + .pos_start_ports) line means a new port is declared
if ((line_start_ports <= line_nb) and (pos_dot != -1)):
port_indentation = len(line) - len(line.lstrip())
port_pos += 1
# 1. the first port in the module instance
# 2. anywhere inside the instance, but not the first or last
# 3. when .port());
# 4. last port when .port()\n and ); is on the next line
# no situation or error situation
situation = 0
inst_closed = False
if (pos_closing != -1):
inst_closed = True
# 1st situation
if (port_pos == 1):
situation = 1
else:
# 3rd situation
if ((pos_dot != -1) and inst_closed):
situation = 3
else:
# 4th situation
if ((pos_dot != -1) and (pos_comma == -1) and (not inst_closed)):
situation = 4
else:
# 2nd situation
if ((pos_dot != -1) and (pos_comma != -1) and (not inst_closed)):
situation = 2
else:
lw.append(occurrence_item.path + " : " + str(line_nb) + " problem when finding the situation")
if (situation != 0):
# the rest of the ports must have the same indentation as the previous line
if (port_indentation - spaces_nb != 2):
avoid_indentation_check = False
if ((line.find("({") != -1) or (line.find("})") != -1)):
avoid_indentation_check = True
if (not avoid_indentation_check):
lw.append(occurrence_item.path + " : " + str(line_nb) + " indentation inside module instance")
else:
# if inside the parameters list
if (occurrence_item.line <= line_nb and line_nb < line_start_ports and (pos_dot != -1)):
param_indentation = len(line) - len(line.lstrip())
if (param_indentation - start_spaces_nb != 2):
lw.append(occurrence_item.path + " : " + str(line_nb) + " indentation inside parameters list")
line_nb += 1
if (line_nb > occurrence_item.line_end):
break
lw_last_size = len(lw)
if (lw_last_size == lw_initial_size + 1):
lw.pop()
###############################################################################
#
# Check guideline for Verilog files in repository
###############################################################################
## all files given as parameters to the script (or all files from repo
## if no flag is specified)
modified_files = []
error_files = []
edit_files = False
guideline_ok = True
# detect all modules from current directory (hdl)
all_modules = detect_all_modules("./")
xilinx_modules = []
xilinx_modules.append("ALT_IOBUF")
xilinx_modules.append("BUFG")
xilinx_modules.append("BUFG_GT")
xilinx_modules.append("BUFGCE")
#xilinx_modules.append("BUFGCE_1")
xilinx_modules.append("BUFGCE_DIV")
xilinx_modules.append("BUFGCTRL")
xilinx_modules.append("BUFGMUX")
#xilinx_modules.append("BUFGMUX_1")
xilinx_modules.append("BUFGMUX_CTRL")
xilinx_modules.append("BUFIO")
xilinx_modules.append("BUFR")
xilinx_modules.append("GTHE3_CHANNEL")
xilinx_modules.append("GTHE4_CHANNEL")
xilinx_modules.append("GTYE4_CHANNEL")
xilinx_modules.append("GTXE2_CHANNEL")
xilinx_modules.append("IBUFDS")
xilinx_modules.append("IBUFDS_GTE2")
xilinx_modules.append("IBUFDS_GTE3")
xilinx_modules.append("IBUFDS_GTE4")
xilinx_modules.append("IBUFDS_GTE5")
xilinx_modules.append("IBUFG")
xilinx_modules.append("IDDR")
xilinx_modules.append("IDELAYCTRL")
xilinx_modules.append("ISERDESE2")
xilinx_modules.append("OBUFDS")
xilinx_modules.append("ODDR")
xilinx_modules.append("system_bd")
xilinx_modules.append("system_wrapper")
# if there is an argument specified
if (len(sys.argv) > 1):
# -m means a file name/s will be specified (including extension!)
# mostly used for testing manually, changing the folder_path
# -me means that it will also modify the files
if (sys.argv[1] == "-m" or sys.argv[1] == "-me"):
if (sys.argv[1] == "-me"):
edit_files = True
arg_nb = 2
while (arg_nb < len(sys.argv)):
# look in the folder_path = current folder
for folder, dirs, files in os.walk("./"):
for name in files:
if((name == sys.argv[arg_nb]) and (check_filename(name))):
#module_path = os.path.abspath(os.path.join(folder, sys.argv[arg_nb]))
module_path = os.path.join(folder, sys.argv[arg_nb])
modified_files.append(module_path)
arg_nb += 1
# -p means a path/s will be specified
# mostly used for github action
# -pe means that it will also modify the files
if (sys.argv[1] == "-p" or sys.argv[1] == "-pe"):
if (sys.argv[1] == "-pe"):
edit_files = True
arg_nb = 2
while (arg_nb < len(sys.argv)):
if (os.path.exists(sys.argv[arg_nb])):
if (check_filename(sys.argv[arg_nb])):
modified_files.append(sys.argv[arg_nb])
else:
error_files.append(sys.argv[arg_nb])
arg_nb += 1
# -e means it will be run on all files, making changes in them
if (sys.argv[1] == "-e"):
edit_files = True
modified_files = detect_all_modules("./")
else:
## if there is no argument then the script is run on all files,
## and without making changes in them
edit_files = False
modified_files = detect_all_modules("./")
# no matter the number of arguments
if (len(modified_files) <= 0):
print("NO detected modules")
guideline_ok = True
sys.exit(0)
else:
for module_path in all_modules:
module_name = get_file_name(module_path)
# list of warnings
lw = []
# if the detected module is between the modified files
if (string_in_list(module_path, modified_files)):
module_name = get_and_check_module(module_path, lw, edit_files)
file_name = get_file_name(module_path)
# file_name is without the known extension, which is .v
if (module_name != file_name):
# applies only to the library folder
if (module_path.find("library") != -1):
guideline_ok = False
error_files.append(module_path)
## system_top modules won't be instantiated anywhere in other
## Verilog or SystemVerilog files
if (module_path.find("system_top") == -1):
# will search for instances only in the files given as arguments
occurrences_list = find_occurrences("./", module_name, modified_files)
if (len(occurrences_list) > 0):
for occurrence_item in occurrences_list:
check_guideline_instances(occurrence_item, lw)
if (len(lw) > 0):
guideline_ok = False
print ("\n -> For %s in:" % module_path)
for message in lw:
print(message)
for module_name in xilinx_modules:
lw = []
xilinx_occ_list = find_occurrences("./", module_name, modified_files)
if (len(xilinx_occ_list) > 0):
for xilinx_occ_it in xilinx_occ_list:
# if the xilinx module was found in the files that are of interest
for it in all_modules:
if (xilinx_occ_it.path == it):
# only then to check the guideline
check_guideline_instances(xilinx_occ_it, lw)
if (len(lw) > 0):
title_printed = False
for message in lw:
if (list_has_substring(modified_files, message)):
if (not title_printed):
print ("\n -> For %s in:" % module_name)
title_printed = True
guideline_ok = False
print(message)
if (error_files):
error_in_library = False
for file in error_files:
## for files in /projects folder,
## the module - file name check doesn't matter
if (file.find("library") != -1):
error_in_library = True
if (error_in_library):
guideline_ok = False
print ("Files with name errors:")
for file in error_files:
## for files in /projects folder,
## the module - file name check doesn't matter
if (file.find("library") != -1):
print (file)
if (not guideline_ok):
print("\nGUIDELINE RULES ARE NOT FOLLOWED\n")
sys.exit(1)
else:
sys.exit(0)