1134 lines
44 KiB
Python
1134 lines
44 KiB
Python
#!/usr/bin/env python3
|
|
|
|
##############################################################################
|
|
########################### HOW TO USE THIS SCRIPT ###########################
|
|
##############################################################################
|
|
##
|
|
## 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/.sv and it should not be some certain files.
|
|
# Returns true or false.
|
|
###############################################################################
|
|
def check_filename (filename):
|
|
|
|
if (filename.endswith('.v') == False and filename.endswith('.sv') == False):
|
|
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_license (list_of_lines, lw, edit_files):
|
|
|
|
currentYear = datetime.now().year
|
|
license_header = """// ***************************************************************************
|
|
// ***************************************************************************
|
|
// Copyright """ + str(currentYear) + """ (c) 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/master/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 [13-23] is the range of years
|
|
## or [13-16] is the year
|
|
aux = list(list_of_lines[line_nb])
|
|
# match a year range
|
|
match = re.match(r'.*(Copyright\s20[0-9]{2}\s[-]\s20[0-9]{2}\s\(c\))', list_of_lines[line_nb])
|
|
if (match is not None):
|
|
# only the last year must be updated (chars [20-23])
|
|
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[20] == c1 and aux[21] == c2 and aux[22] == c3 and aux[23] == c4):
|
|
changed = False
|
|
else:
|
|
aux[20] = c1
|
|
aux[21] = c2
|
|
aux[22] = c3
|
|
aux[23] = c4
|
|
changed = True
|
|
else:
|
|
# match a single year
|
|
match = re.match(r'.*(Copyright\s20[0-9]{2}\s\(c\))', list_of_lines[line_nb])
|
|
|
|
if (match is not None):
|
|
## if the year is different than the currentYear,
|
|
## then must make a year range [13-23]
|
|
year = aux[13] + aux[14] + aux[15] + aux[16]
|
|
if (year != str(currentYear)):
|
|
aux.insert(17, ' ')
|
|
aux.insert(18, '-')
|
|
aux.insert(19, ' ')
|
|
aux.insert(20, str(get_digit(currentYear, 3)))
|
|
aux.insert(21, str(get_digit(currentYear, 2)))
|
|
aux.insert(22, str(get_digit(currentYear, 1)))
|
|
aux.insert(23, str(get_digit(currentYear, 0)))
|
|
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 cannot be updated and header is not up-to-date
|
|
if (not edit_files and changed and template_matches):
|
|
lw.append(module_path + " : license header is not updated")
|
|
header_status = 3
|
|
|
|
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_license(list_of_lines, lw, edit_files)
|
|
# GC: check if the license header is updated
|
|
if (header_status == -1):
|
|
edited = False
|
|
lw.append(module_path + " : license header 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 not respected\n")
|
|
sys.exit(1)
|
|
else:
|
|
sys.exit(0)
|