#!/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: #// #// #// 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)