VHDL Writer

VHDLWriter writes elements of a VHDL language model as VHDL source text. It works on models built by hand as well as on models created by a parser like pyGHDL.dom.

from pyVHDLModel.Association     import ParameterAssociationItem
from pyVHDLModel.Expression      import IntegerLiteral
from pyVHDLModel.Formatting.VHDL import VHDLWriter
from pyVHDLModel.Name            import SimpleName, ParenthesisName
from pyVHDLModel.Symbol          import IndexedObjectOrFunctionCallSymbol, SimpleObjectOrFunctionCallSymbol

call = IndexedObjectOrFunctionCallSymbol(ParenthesisName(SimpleName("resize"), [
  ParameterAssociationItem(None, SimpleObjectOrFunctionCallSymbol(SimpleName("count"))),
  ParameterAssociationItem(SimpleName("new_size"), IntegerLiteral(8))
]))

writer = VHDLWriter()
print(writer.Format(call))   # resize(count, new_size => 8)

Writing is a re-format, not a round-trip: comments, blank lines, the letter case of reserved words and the notation of abstract literals (e.g. 16#FF# becomes 255) are not preserved.

Supported language constructs

The writer formats names, symbols, literals, expressions, association items, subtype indications, object declarations, interface items, component declarations, mode view declarations, type and subtype declarations, alias declarations, attribute declarations and attribute specifications, subprograms, subprogram instantiations and protected types (Declarations), context items, design units and documents (Design Units), concurrent signal assignments, concurrent procedure calls, concurrent assertions, processes, instantiations, block statements and generate statements (Concurrent Statements), and sequential statements (Sequential Statements).

Visitor

The writer is a Visitor. A model element walks itself (Visit()): it reports Visit<Class> to the visitor, walks its children in source order and reports Depart<Class>. A token between two parts, which is not a model element itself, is reported by an event named after the token, e.g. VisitArrow for => or VisitComma for ,. A part of an element, which is not a model element itself, is reported by an event pair named after its production in IEEE Std 1076-2008, e.g. VisitGenericClause and DepartGenericClause around the generics of a component. The writer appends the text of each event to a buffer, which Format() returns. A parser’s subclass of a model class inherits the walk of that model class, so it’s written like that model class.

An element of a class without a walk of its own raises NotImplementedError when it is visited. The writer raises VHDLWriterError for an element without VHDL notation, e.g. a string literal containing a control character or an aggregate with a single positional element. The writer never skips an element. The exception’s Element property returns the offending element.

A symbol is written from its name, whether it’s resolved or not.

Every event of Visitor does nothing, so another visitor overrides only the events it needs:

from typing              import List

from pyVHDLModel.Name    import SimpleName
from pyVHDLModel.Visitor import Visitor

class SimpleNameCollector(Visitor):
  _identifiers: List[str]

  def __init__(self) -> None:
    super().__init__()
    self._identifiers = []

  def VisitSimpleName(self, name: SimpleName) -> None:
    self._identifiers.append(name.Identifier)

Formatting Options

VHDLStyle holds the formatting options passed to the writer.

keywordCase

Letter case of reserved words - the members of ReservedWord, which the writer writes them from: Lower (default) or Upper. Identifiers keep their letter case. See vhdl-style-guide’s Configuring Uppercase and Lowercase Rules.

indentStyle

Indentation style, as vhdl-style-guide’s option indent_style: Spaces (default, spaces) or SmartTabs (smart_tabs: one tab per indentation level). See vhdl-style-guide’s Configuring Indent Rules.

indentSize

Number of spaces per indentation level, as vhdl-style-guide’s option indent_size (default: 2). It applies to Spaces.

from pyVHDLModel.Formatting.VHDL import IndentStyle, KeywordCase, VHDLStyle, VHDLWriter

writer = VHDLWriter(VHDLStyle(KeywordCase.Upper, IndentStyle.SmartTabs))

Parentheses

The model represents an expression as a tree, so it needs no parentheses. The writer adds the parentheses VHDL’s grammar requires (IEEE Std 1076-2008, clauses 9.1 and 9.2). Parentheses from the source, represented by SubExpression, are kept. Each operator class names its precedence level (OperatorPrecedence) in the class variable _PRECEDENCE.

from pyVHDLModel.Expression      import AdditionExpression, MultiplyExpression
from pyVHDLModel.Formatting.VHDL import VHDLWriter
from pyVHDLModel.Name            import SimpleName
from pyVHDLModel.Symbol          import SimpleObjectOrFunctionCallSymbol

def obj(name: str) -> SimpleObjectOrFunctionCallSymbol:
  return SimpleObjectOrFunctionCallSymbol(SimpleName(name))

expression = MultiplyExpression(AdditionExpression(obj("a"), obj("b")), obj("c"))
print(VHDLWriter().Format(expression))   # (a + b) * c
Precedence levels (OperatorPrecedence), loosest first

Level

Operators

Operands without parentheses

Logical

and, or, xor, xnor, nand, nor

relations; a chain of the same operator, except nand and nor

Relational

=, /=, <, <=, >, >=, ?=, ?/=, ?<, ?<=, ?>, ?>=

shift expressions

Shift

sll, srl, sla, sra, rol, ror

simple expressions

Adding

+, -, &, and the signs +, -

left: simple expression; right: term; a sign’s operand: term

Multiplying

*, /, mod, rem

left: term; right: factor

Miscellaneous

**, abs, not, unary logical operators

primaries

Examples:

  • (a + b) * c and a + b * c

  • a - (b - c) and a - b - c

  • a ** (b ** c), because a ** b ** c is illegal

  • (a and b) or c, because different logical operators can’t be mixed

  • a + (-b), because a sign can only start a simple expression; a negative number literal is written with a sign, too: a - (-1)

Declarations

A declaration is written on its own line, indented by its nesting level, and ends with a newline. A declaration containing other items (e.g. a component declaration) writes each item on its own line, indented one level deeper, and ends with end, the reserved word of the declaration and its identifier:

component counter is
  generic (
    BITS : positive := 8;
    type T
  );
  port (
    clk, rst : in bit;
    q : out T
  );
end component counter;
Subtype indications

A constrained subtype symbol is written with its constraint: a range constraint (ConstrainedScalarSubtypeSymbol, integer range 0 to 7), an index constraint (ConstrainedArraySubtypeSymbol, bit_vector(7 downto 0)) or a record constraint (ConstrainedRecordSubtypeSymbol, frame_t(data(7 downto 0))).

Object declarations

Constants, deferred constants, variables, shared variables, signals and files are written with their identifiers, their subtype indication and, if present, their default expression. The model has no file open information, so a file declaration is written without one.

Interface items

Each item of a generic clause or a port clause is written on its own line; the items are separated by semicolons. A clause without items is not written. Generic constants and ports are written without an object class reserved word (BITS : positive, clk : in bit), parameters with one (signal clk : in bit). An item with Mode.Default is written without a mode. Ports and parameters with a mode view (VHDL-2019) are written with the reserved word view (link : view master).

Mode view declarations

A mode view declaration (VHDL-2019) is written with one element per line. An element without a mode raises VHDLWriterError.

Type and subtype declarations

Enumerated, integer, floating-point, physical, array, record, access, file and incomplete type declarations (AnonymousType, type node;) and subtype declarations. A physical type writes its units and a record type its element declarations one per line. An index of an array type, which is a SimpleSubtypeSymbol, is an unbounded index (natural range <>); any other index is a range. The model’s resolution function of a subtype declaration is not written.

Alias and attribute declarations

Alias declarations with and without subtype indication, attribute declarations and attribute specifications. The entity class EntityClass.Others has no VHDL notation and raises VHDLWriterError.

Subprograms

The model doesn’t distinguish a subprogram declaration from a subprogram body. A procedure or function with declared items or statements is written as a subprogram body. One without them is written as a subprogram declaration (procedure reset;), if its parent is a package declaration, a protected type declaration or none, otherwise as an empty subprogram body (e.g. in a package body or an architecture). A subprogram body has a declarative part and a statement part. Each parameter is written on its own line. A generic clause of a subprogram is followed by the reserved word parameter before the parameter list. Generic procedures and functions are written in a generic clause like any other generic. A method of a protected type is written like a procedure or function.

Subprogram instantiations

Procedure and function instantiations are written with their generic map aspect, one association per line.

Protected types

A protected type declaration writes its methods, a protected type body its declared items, one per line.

function maximum generic (
  type T
) parameter (
  constant a, b : T
) return T;

Documentation comments of the model (Documentation) are not written.

Design Units

Format() of a Document writes all its design units in the document’s order, separated by a blank line. Each design unit is preceded by its context items (library clauses, use clauses and context references), followed by a blank line.

library ieee;
use ieee.std_logic_1164.all;

entity counter is
  generic (
    BITS : positive := 8
  );
  port (
    clk : in std_logic;
    count : out natural
  );
end entity counter;

architecture rtl of counter is
  signal value : natural := 0;
begin
end architecture rtl;

Entities, architectures, packages, package bodies, package instantiations, context declarations and configurations are written with end, the reserved words of the design unit and its identifier. An entity without statements has no begin.

A configuration writes its block configuration, nested block configurations and component configurations, each ending with end for;. A binding indication (use entity work.sub(rtl)) and its map aspects are indented one level deeper than its component configuration; each association of a map aspect is written on its own line.

Concurrent Statements

A concurrent statement is written on its own line, starting with its label, if any (mux : ...).

Signal assignments

Simple, conditional and selected signal assignments are written on one line. The waveform elements of a waveform are separated by commas, a delay follows the reserved word after. The alternatives of a conditional signal assignment are separated by the reserved word else, the alternatives of a selected signal assignment by commas; choices are separated by vertical lines (when 1 | 2 to 3).

Procedure calls and assertions

A concurrent procedure call is written with its parameter associations in parentheses, if it has any; an assertion with its report and severity, if given.

Processes

A process is written with its sensitivity list, if any, the reserved word is, its declared items, begin and its statements. A sensitivity list consisting of a single AllName without prefix is written as process (all).

count : process (clk, rst) is
  variable value : natural := 0;
begin
end process count;
Instantiations

Component, entity and configuration instantiations are written with the reserved word component, entity or configuration, followed by their generic and port map aspects, one association per line. An instantiation without label raises VHDLWriterError.

Block statements

A block statement writes its block header (generic clause, generic map aspect, port clause, port map aspect), its declared items, begin and its statements; each map aspect of the header ends with a semicolon.

Generate statements

If-generate, case-generate and for-generate statements are written with their branches or alternatives and end generate. A branch or alternative with declared items writes begin before its statements; alternative labels are written before the condition or the choices (elsif middle : BITS > 4 generate). A block or generate statement without label raises VHDLWriterError.

gen : case MODE generate
  when 0 | 1 to 2 =>
    q <= a;
  when others =>
    signal t : bit;
  begin
    q <= t;
end generate gen;

Sequential Statements

A sequential statement is written on its own line in the statement part of a process or subprogram body, starting with its label, if any.

Assignments

Simple, conditional and selected signal and variable assignments, force assignments (q <= force '1';) and release assignments (q <= release;). The alternatives are written as for concurrent signal assignments.

Other statements

Procedure calls, report statements, assertions, null statements, return statements with and without value, and wait statements with their sensitivity clause (on), condition clause (until) and timeout clause (for), if given.

Compound statements

If statements with their branches, case statements with their alternatives, and loops (for, while and without iteration scheme) write the statements they contain on their own lines, indented one level deeper, and end with end if, end case or end loop and the label, if any. Next and exit statements are written with their condition, if any; the model keeps no loop label for them, so none is written.

p : process is
  variable v : natural := 0;
begin
  increment : v := v + 1;
  q <= d after 1 ns;
  outer : for i in 0 to 3 loop
    next when skip;
    if v = 0 then
      null;
    end if;
  end loop outer;
  wait on d until d = '1' for 10 ns;
end process p;