GSoC #8: MyHDL Back-end Source Code Analysis (1)

The original blog post is here.


In MyHDL source code, “front-end” means the simulation part, and “back-end” usually means the conversion (MyHDL to Verilog or VHDL) part. The code of conversion part is in myhdl/conversion path, while the unit tests are in myhdl/test/conversion path.

Initialization First

First, let’s see myhdl/conversion/__init__.py in this directory about what it provides to users:

from __future__ import absolute_import
from ._verify import verify, analyze, registerSimulator
from ._toVerilog import toVerilog
from ._toVHDL import toVHDL

__all__ = ["verify",
           "analyze",
           "registerSimulator",
           "toVerilog",
           "toVHDL"
           ]

This is rather simple. The most useful things we provide are analyze, verify, toVerilog, and toVHDL. If you don’t know about the usage of these methods, please refer to related contents in MyHDL documentation.

Also, we could see that the function analyze does not come from _analyze.py. Instead, it comes from _verify.py. The reason will be explained in later posts for _verify.py.

In _misc.py, some helper functions are provided. It is better to examine them when we need them.

What Do We Call When We Call toVerilog

Let’s get a head start of the whole back-end from _toVerilog.py. We can see that in __init__.py, it imports a toVerilog object in Verilog. So, what is toVerilog?

In _toVerilog.py, we could find a line of its definition:

toVerilog = _ToVerilogConvertor()

So it is an instance of _ToVerilogConvertor.

In the definition of _ToVerilogConvertor, it has four methods: __init__, __call__, _cleanup, and _convert_filter. The last two methods are helpers for the code in __call__. We can ignore them at this time.

When importing myhdl, the previously mentioned toVerilog = _ToVerilogConvertor() will be executed, so that _ToVerilogConvertor.__init__ will be called at this time. When user code calls toVerilog such as toVerilog(...), it is actually _ToVerilogConvertor.__call__(...). So, even though toVerilog looks like a function while using, it is actually implemented by the class _ToVerilogConvertor.

We could see a lot of examples of this kind of design pattern.

In _ToVerilogConvertor.__init__, it only defines some necessary attributes for conversion. It is similar to _ToVerilogConvertor._cleanup.

In _ToVerilogConvertor.__call__, the code is longer, so some necessary checks and initializations will be skipped in the analysis. It works as following steps:

  1. This method first defines the file name and its path, and open it as a file called vfile.

  2. It extracts a flattened list of arguments of the whole hierarchy. Also a list of signals and a list of generators will be analyzed. And then, it infers the interface of the top module.

  3. It extracts document string from the top level by calling inspect module, which is a default module in Python.

  4. It writes the file header, the module header, the signal declarations in vfile by using the extracted interface and the list of signals above.

  5. It converts generators to Verilog code by visiting the abstract syntax tree (AST) of it. The conversion from Python code to AST is done by the module ast, which is provided in the default Python implementation. The Verilog code will be written into vfile.

  6. It writes the module footer endmodule to vfile.

  7. It closes vfile.

  8. If the test bench is valid for the code, it will also converts the test bench into Verilog.

  9. It builds a port map from the interface for co-simulation.

  10. Cleanup.

Note: If you don’t know what an AST is, please refer to here as a tutorial. This concept is very important in MyHDL back-end.

Functions provided in _analyze.py is heavily used in _ToVerilogConvertor.__call__. Some other code, even if they are not in myhdl/conversion, are called in the implementation. So, if you want to make every details clear, it is necessary to read the code of the whole project.

Walking Through AST

To see how AST has been walked through, it is better to take a look at method _convertGens at first. It uses with different visitors when the tree is in different kinds of blocks, like always, always_comb, always_seq, etc. However, all of these visitors are derived from _ConvertVisitor, with only a few differences.

_ConvertVisitor is derived from ast.NodeVisitor. We could see that it has different types of methods for visiting different types of AST nodes. The visitor will walk through the tree by using these methods.

Since there is already many types of AST nodes, and the code is not difficult, it is recommended to read the code and see what does the convertor do during visiting different kinds of nodes if you are interested in it.

For the documentation of different types of nodes, please refer to Green Tree Snake.