Localizing

VSG supports customization to your coding style standards by allowing localized rules. These rules are stored in a directory with an __init__.py file and one or more python files. The files should follow the same structure and naming convention as the rules found in the vsg/rules directory.

The localized rules will be used when the –local_rules command line argument is given or using the local_rules option in a configuration file.

Example: Create rule to check for entity and architectures in the same file.

Let’s suppose in our organization the entity and architecture should be split into separate files. This rule is not in the base rule set, but we can add it through localization. For this example, we will be setting up the localized rules in your home directory.

Prepare local rules directory

Create an empty directory with an empty __init__.py file

$ mkdir ~/local_rules
$ touch ~/local_rules/__init__.py

Create new rule file

We will create a new rule by extending the base rule class.

Note

The file name and class name must start with rule_. Otherwise VSG will not recognize it as a rule.

The rule will be in the localized group. Since this is the first rule, we will number it 001.

from vsg import rule


class rule_001(rule.rule):

  def __init__(self):
      rule.rule.__init__(self, 'localized', '001')

Referencing the Phases, we decide it should be in phase 1: structural.

from vsg import rule


class rule_001(rule.rule):

  def __init__(self):
      rule.rule.__init__(self, 'localized', '001')
      self.phase = 1

Now we need to add the analyze method to perform the check.

from vsg import rule


class rule_001(rule.rule):

  def __init__(self):
      rule.rule.__init__(self, 'localized', '001')
      self.phase = 1

  def analyze(self, oFile):

The built in variables in the vsg.line class can be used to build rules. There are helper functions in vsg.utilities, vsg.check, and vsg.fix also. In this case, the vsg.vhdlFile class has two attributes (hasEntity and hasArchitecture) that are exactly what we need. We are ready to write the body of the analyze method:

from vsg import rule


class rule_001(rule.rule):

  def __init__(self):
      rule.rule.__init__(self, 'localized', '001')
      self.phase = 1

  def analyze(self, oFile):
      if oFile.hasEntity and oFile.hasArchitecture:
          self.add_violation(1)

The base rule class has an add_violation method which takes a line number as an argument. This method appends the line number to a violation list, which is processed later for reporting and fixing purposes. In this case, any line number will do so we picked 1.

We must decide if we want to give VSG the ability to fix this rule on it’s own. If so, then we will need to write the _fix_violations method. However, for this violation we want the user to split the file. We will tell VSG the rule is not fixable.

from vsg import rule


class rule_001(rule.rule):

  def __init__(self):
      rule.rule.__init__(self, 'localized', '001')
      self.phase = 1
      self.fixable = False  # User must split the file

  def analyze(self, oFile):
      if oFile.hasEntity and oFile.hasArchitecture:
          self.add_violation(1)

We also need to provide a solution to the user so they will know how to fix the violation:

from vsg import rule


class rule_001(rule.rule):

  def __init__(self):
      rule.rule.__init__(self, 'localized', '001')
      self.phase = 1


      self.fixable = False  # User must split the file
      self.solution = 'Split entity and architecture into seperate files.'

  def analyze(self, oFile):
      if oFile.hasEntity and oFile.hasArchitecture:
          self.add_violation(1)

Finally, we need to add a code tag check so the rule can be disabled via comments in the code:

from vsg import rule


class rule_001(rule.rule):

  def __init__(self):
      rule.rule.__init__(self, 'localized', '001')
      self.phase = 1
      self.fixable = False  # User must split the file
      self.solution = 'Split entity and architecture into seperate files.'

  def analyze(self, oFile):
      if not self.is_vsg_off(oLine):
          if oFile.hasEntity and oFile.hasArchitecture:
              self.add_violation(1)

The rule is complete, so we save it as rule_localized_001.py. Performing an ls on our local_rules directory:

$ ls ~/local_rules
__init__.py  rule_localized_001.py

Use new rule to analyze

When we want to run with localized rules, use the –local_rules option.

$ vsg -f RAM.vhd --local_rules ~/local_rules
File:  RAM.vhd
==============
Phase 1... Reporting
localized_001            |            1 | Split entity and architecture into seperate files.
Phase 2... Not executed
Phase 3... Not executed
Phase 4... Not executed
Phase 5... Not executed
Phase 6... Not executed
Phase 7... Not executed
==============
Total Rules Checked: 50
Total Failures:      1

Our new rule will now flag files which have both an entity and an architecture in the same file. That was a fairly simple rule. To write more complex rules, it is important to understand how the rule class works.

Understanding the Rule class

Every rule uses the base rule class. There are a few methods to the base rule class, but we are interested in only the following:

Method Description
add_violations Adds violations to a list.
analyze Calls _pre_analyze and then _analyze.
_analyze Code that performs the analysis.
fix calls analyze and then _fix_violations.
_fix_violations Code that fixes the violations.
_get_solution Prints out the solution to stdout.
_pre_analyze Code that sets up variables for _analyze.

We will look at the rule constant_014 to illustrate how VSG uses the methods above:

class rule_014(rule.rule):
    '''
    Constant rule 014 checks the indent of multiline constants that are not arrays.
    '''

    def __init__(self):
        rule.rule.__init__(self)
        self.name = 'constant'
        self.identifier = '014'
        self.solution = 'Align with := keyword on constant declaration line.'
        self.phase = 5

    def _pre_analyze(self):
        self.alignmentColumn = 0
        self.fKeywordFound = False

    def _analyze(self, oFile, oLine, iLineNumber):
        if not oLine.isConstantArray and oLine.insideConstant:
            if oLine.isConstant and ':=' in oLine.line:
                self.alignmentColumn = oLine.line.index(':=') + len(':= ')
                self.fKeywordFound = True
            elif not oLine.isConstant and self.fKeywordFound:
                sMatch = ' ' * self.alignmentColumn
                if not re.match('^' + sMatch + '\w', oLine.line):
                    self.add_violation(iLineNumber)
                    self.dFix['violations'][iLineNumber] = self.alignmentColumn
            if oLine.isConstantEnd:
                self.fKeywordFound = False

    def _fix_violations(self, oFile):
        for iLineNumber in self.violations:
            sLine = oFile.lines[iLineNumber].line
            sNewLine = ' ' * self.dFix['violations'][iLineNumber] + sLine.strip()
            oFile.lines[iLineNumber].update_line(sNewLine)

Creating Class

First we create the rule by inheriting from the base rule class. We also add a comment to describe what the rule is doing.

class rule_014(rule.rule):
    '''
    Constant rule 014 checks the indent of multiline constants that are not arrays.
    '''

Adding __init__

Then we add the __init__ method. It calls the init of the base rule class, then we modify attributes for this specific rule:

def __init__(self):
    rule.rule.__init__(self)
    self.name = 'constant'
    self.identifier = '014'
    self.solution = 'Align with := keyword on constant declaration line.'
    self.phase = 5

For this rule we set it’s name, identifier, solution, and phase.

Analyzing Considerations

The analyze method of the base rule class will first call _pre_anaylze before _analyze. The _analyze method is wrapped in a loop that increments through each line of the file. The analyze method also checks if the rule has been turned off for a line, via code tags. If the code tag indicates to ignore the line, then it will be skipped. If you decide to override the analyze method, then you should add the code tag check.

Adding _pre_analyze method

In this rule, we use the _pre_analyze method to initialize some variables. These variables must be set outside the loop that is present in the analyze method.

def _pre_analyze(self):
    self.alignmentColumn = 0
    self.fKeywordFound = False

Adding _analyze method

The _analyze method is called on every line of the VHDL file. Any memory needed between lines must be declared in the _pre_analyze method. In the following code, notice self.alignmentColumn and self.fKeywordFound.

def _analyze(self, oFile, oLine, iLineNumber):
    if not oLine.isConstantArray and oLine.insideConstant:
        if oLine.isConstant and ':=' in oLine.line:
            self.alignmentColumn = oLine.line.index(':=') + len(':= ')
            self.fKeywordFound = True
        elif not oLine.isConstant and self.fKeywordFound:
            sMatch = ' ' * self.alignmentColumn
            if not re.match('^' + sMatch + '\w', oLine.line):
                self.add_violation(iLineNumber)
                self.dFix['violations'][iLineNumber] = self.alignmentColumn
        if oLine.isConstantEnd:
            self.fKeywordFound = False

This code is searching for the characteristics of a non-array constant.

def _analyze(self, oFile, oLine, iLineNumber):
    if not oLine.isConstantArray and oLine.insideConstant:

Once the non-array constant is found, it notes the column of the := keyword.

if oLine.isConstant and ':=' in oLine.line:
    self.alignmentColumn = oLine.line.index(':=') + len(':= ')
    self.fKeywordFound = True

On successive lines of the constant declaration, it checks to see if there are enough spaces from the beginning of the line to match the column number the := is located at.

elif not oLine.isConstant and self.fKeywordFound:

If there are not enough spaces, then a violation is added. We also store off the required column into a predefined dictionary named dFix. This will be used later when the fix method is called.

sMatch = ' ' * self.alignmentColumn
if not re.match('^' + sMatch + '\w', oLine.line):
    self.add_violation(iLineNumber)
    self.dFix['violations'][iLineNumber] = self.alignmentColumn

When we detect the end of the constant declaration, we clear a flag and prepare for the next constant declaration.

if oLine.isConstantEnd:
    self.fKeywordFound = False

Fixing considerations

The fix method will first call the analyze method and then the _fix_violations method. Unlike the analyze method, it does not wrap the _fix_violations in a loop. This is due to some fixes needing to execute either top down or bottom up. Rules that add or delete lines need to work from the bottom up. Otherwise, the violations detected by the analyze method will have moved.

Adding the _fix_violations method

In this rule, we are going to iterate on all the violations in the self.violations attribute.

def _fix_violations(self, oFile):
    for iLineNumber in self.violations:

We store the current line off to make it easier to read. Then we strip the line of all leading and trailing spaces and prepend the number of spaces required to align with the := keyword.

sLine = oFile.lines[iLineNumber].line
sNewLine = ' ' * self.dFix['violations'][iLineNumber] + sLine.strip()

Finally, we update the line with our modified line using the update_line method.

oFile.lines[iLineNumber].update_line(sNewLine)

Rule creation guidelines

Keep these points in mind when creating new rules:

  1. Use an existing rule as a starting point
  2. Remember that analyze calls _pre_analyze and then _analyze
  3. Override _get_solution to return complex messages
  4. analyze method can be overridden if necessary
  5. If overriding analyze, then include a check for vsg_off