over 4 years ago

This is a summary of the Google Summer of Code project MyHDL: Fixed-point Compiler.


The entire code can be found in the mep-111 branch of my repository. It has been contained in a single pull request #228 and not yet merged.

The main repository of MyHDL has created a gsoc branch for GSoC projects this year. However, none of the code has been merged in until now.

Summary of Goals

The goals have been described simply in my proposal:

When the project finishes, MyHDL should have a complete implementation of fixbv and its compiler backend with complete documentation, include tutorials and reference.

It contains 3 parts: front-end, back-end, and documentation. To simply speaking, it is far from saying that the project has been "completed". Although the front-end has been finished, the back-end is still not working, and although I have written many blog posts about my work and the implementation of MyHDL, the documentation of fixbv interface at this time temporarily is still MEP-111.

Here is a detailed description of each part.


Before my project, cfelton has implemented a simple fixbv front-end. It only supports some simple functions. So I made the following improvements:

  1. Added support for Python 3.x.
  2. Added support for point alignment.
  3. Added support for round modes and overflow modes.
  4. Added more tests for fixbv front-end in test_fixbv.py.

These improvements made an almost complete change on the previous fixbv implementation, and also affected some other critical code, such as _Signal.py, etc.

It has only some minor issues left now. Because those issues do not affect the core function of fixbv, the implementation of these issues has been put in a lower priority after discussion with mentors.

  1. There are still some minor issues marked as @todo after cleaned up cfelton's code.
  2. Implementation of fxsum which is described in a previous post. Since it only affects intermediate results, it seems not necessary.
  3. Interoperability with intbv. Since intbv could be treated as a fixbv without a fractional part, it is possible to operate between intbv and fixbv. It has been given a low priority since it is not defined in MEP-111 and not so emergent at this time.


At this time, only the tests of Verilog conversion has been implemented. There are a few patches on the back-end code but it is still not working even with those simple test cases.

There are still something that needs to go:

  1. Patch AST node visitors for conversion.
  2. Patch other parts of the back-end code in order to implement all the functions of fixbv.
  3. Make bigger test cases to better check the implementation, such as CORDIC.


Until now, the main documentation of fixbv is MEP-111 of MyHDL project, which is written by cfelton.

I have written some blog posts of some features and issues of fixbv. However, this part has not been merged into a larger documentation. It will be helpful in the future.

Reasons of Incomplete

  1. The plan is too ambitious. I thought it was just a new type on this existing platform. However, I should also review many other parts of work according to different issues that may take.
  2. Too much hanging around in the ad hoc issues. While implementing the front-end, there were so many small issues and I have to take time to fix them.
  3. Not enough work time. GSoC needs 40 hours per week to work for the project. However, I am in a university located in Japan, and I have to deal with final exams in June and August for spring and summer quarter respectively. It took me a lot of time. I have suggested future GSoC to consider students' time in different regions.

Future Plan

  1. Fully implement the back-end with tests.
  2. Merge the documentation to some places like The MyHDL Manual.
  3. Implement interoperability between intbv and fixbv.

I have deeply involved in the society of MyHDL. I will contribute to it in the future.

over 4 years ago

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",

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.

over 4 years ago

Comparing to round modes I introduced in the last post, overflow modes are rather simple.

There are only 3 overflow modes in cfelton's _resize.py: saturate, ring, and wrap.

saturate means that if overflow occurs, the value will remain maximum (minimum if underflow).

ring and wrap behaves the same. If overflow occurs, it then builds up from minimum. Similarly, if underflow occurs, it goes down from maximum. For binary implementation, it is just reserve the last bits in its width.

We can see clearly in the original implementation:

def _overflow(val, fmt, overflow_mode):
    """handle overflow"""

    assert is_overflow_mode(overflow_mode)
    wl,iwl,fwl = fmt
    mm = 2**(wl-1)
    mmin,mmax = -mm,mm
    #print("    [rsz][ovl]: %f %d %d, %s" % (val, mmin, mmax, fmt))

    if overflow_mode == 'saturate':
        if val >= mmax:
            retval = mmax-1
        elif val <= mmin:
            retval = mmin
            retval = val
    elif overflow_mode == 'ring' or overflow_mode == 'wrap':
        retval = (val - mmin) % (mmax - mmin) + mmin
        raise ValueError

    return retval
over 4 years ago

In cfelton's implementation of fixbv, there are some kinds of round modes implemented in _resize.py, as in the source code:

              # round    :
ROUND_MODES = (   # towards  :
'ceil',       # +infinity: always round up
'fix',        # 0        : always down
'floor',      # -infinity: truncate, always round down
'nearest',    # nearest  : tie towards largest absolute value
'round',      # nearest  : ties to +infinity
'convergent', # nearest  : tie to closest even (round_even)
'round_even', # nearest  : tie to closest even (convergent)

The behavior of ceil, fix and floor is quite clear in this case. Whatever fractional part is, it will be rounded to the integer towards +inf, 0, or -inf.

But it seems not quite clear for the last four modes.

OK, let's see the source code in _resize.py first:

def _round(val, fmt, round_mode):
    """Round the initial value if needed"""
    # Scale the value to the integer range (the underlying representation)

    assert is_round_mode(round_mode)
    assert isinstance(fmt, tuple)
    wl,iwl,fwl = fmt
    _val = val
    val = val * 2.0**fwl
    #print("    [rsz][rnd]: %f %f, %s" % (val, _val, fmt))

    if round_mode == 'ceil':
        retval = math.ceil(val)

    elif round_mode == 'fix':
        if val > 0:
            retval = math.floor(val)
            retval = math.ceil(val)

    elif round_mode == 'floor':
        retval = math.floor(val)

    elif round_mode == 'nearest':
        fval,ival = math.modf(val)
        if fval == .5:
            retval = int(val+1) if val > 0 else int(val-1)
            retval = round(val)

    elif round_mode == 'round':
        retval = round(val)
    elif round_mode == 'round_even' or round_mode == 'convergent':
        fval,ival = math.modf(val)
        abs_ival = int(abs(ival))
        sign = -1 if ival < 0 else 1

        if (abs(fval) - 0.5) == 0.0:
            if abs_ival%2 == 0:
                retval = abs_ival * sign
                retval = (abs_ival + 1) * sign
            retval = round(val)

        raise TypeError("invalid round mode!" % self.round_mode)

    return int(retval)

To read the last 4 kinds of resolution, it is necessary to know the behavior of Python's built-in round function.

Here we assume we do not provide ndigits parameter to round function.

Python's document says that round will round the numbers to the nearest integer. However, if the fractional part is 0.5, round will round to the nearest even number. That is to say, round(2.5) will be 2, but round(3.5) will be 4.

So, I guess that the behavior of round modes round, round_even, and convergent are the same.

Finally, nearest will be the same as the above three round modes in negative values, but for positive values, it will be different. If the fractional part is 0.5, it will advance to the larger integer, otherwise round to the nearest.

To verify this, I wrote a small program for it. The rounding code is copied from corresponding function in _resize.py.

import math
import csv

                  # round    :
ROUND_MODES = (   # towards  :
    'ceil',       # +infinity: always round up
    'fix',        # 0        : always down
    'floor',      # -infinity: truncate, always round down
    'nearest',    # nearest  : tie towards largest absolute value
    'round',      # nearest  : ties to +infinity
    'convergent', # nearest  : tie to closest even (round_even)
    'round_even', # nearest  : tie to closest even (convergent)

def _round(val, round_mode):
    if round_mode == 'ceil':
        retval = math.ceil(val)

    elif round_mode == 'fix':
        if val > 0:
            retval = math.floor(val)
            retval = math.ceil(val)

    elif round_mode == 'floor':
        retval = math.floor(val)

    elif round_mode == 'nearest':
        fval,ival = math.modf(val)
        if fval == .5:
            retval = int(val+1) if val > 0 else int(val-1)
            retval = round(val)

    elif round_mode == 'round':
        retval = round(val)

    elif round_mode == 'round_even' or round_mode == 'convergent':
        fval,ival = math.modf(val)
        abs_ival = int(abs(ival))
        sign = -1 if ival < 0 else 1

        if (abs(fval) - 0.5) == 0.0:
            if abs_ival%2 == 0:
                retval = abs_ival * sign
                retval = (abs_ival + 1) * sign
            retval = round(val)

        raise TypeError("invalid round mode!" % self.round_mode)

    return int(retval)

values = [-3.5, -3.14, -3., -2.718, -2.5, -2., -1.618, -1.5, -1., -.618, -.5,
          0., .5, .618, 1., 1.5, 1.618, 2., 2.5, 2.718, 3., 3.14, 3.5]

with open('round_test.csv', 'w', newline='') as csvfile:
    wr = csv.writer(csvfile)
    wr.writerow([''] + values)

    for round_mode in ROUND_MODES:
        rounded_values = [_round(val, round_mode) for val in values]
        wr.writerow([round_mode] + rounded_values)

It tests different values for different round modes.

Here is the result:

EDIT: The code of "nearest" rounding did not handle the case of negative values, so the condition case should be:

if fval == .5 or fval == -.5:

And its behavior when the fractional part is 0.5 (either positive or negative) should be rounding away from 0.

over 4 years ago

The Problem

If you want to sum up several fixbv fixed-point variables, what will you do?

If your answer is as follows:

a + b + c + d

It seems to be correct but there would be a trap on the bit width.

Bit Width Changing While Adding

Here we use (total_width, integer_width, fractional_width) to indicate the bit width of the fixbv variable, its integer part, and its fractional part.

As written in MEP-111, if two fixed-point variables are added, the fractional part of the result should be the longest of the operands; and the integer part of the result should be the longest plus one, in order to avoid overflow.

This is an example of adding a variable in format (8, 3, 4) and another variable in format (8, 0, 7):

+ ssss.fffffff

We can see that the first operand has been added zeros in the tail of the fractional part, and the second operand has been added sign bits in the beginning, in order to perform point alignment.

A Critical Example

Considering the following situation:

>>> a.format
(16, 4, 11)
>>> b.format
(16, 4, 11)
>>> c.format
(8, 3, 4)
>>> d.format
(8, 7, 0)

If we add like a + b + c + d, the format of the final result will be:

 (16, 4, [11])+(16, 4, [11])+(8, 3, [4])+(8, 7, [0])
=(17, 5, [11])+(8, 3, [4])+(8, 7, [0])
=(18, 6, [11])+(8, 7, [0])
=(20, 8, [11])

However, if we add in d + a + b + c, the result should be:

 (8, 7, [0])+(16, 4, [11])+(16, 4, [11])+(8, 3, [4])
=(20, 8, [11])+(16, 4, [11])+(8, 3, [4])
=(21, 9, [11])+(8, 3, [4])
=(22, 10, [11])


If we use the assignment like:

x = a + b + c + d

this problem should be consider. But, if we have already defined the format of x and perform the operation as

x[:] = a + b + c + d

it would be safer because the format of x has already been decided. However, rounding and overflow must be considered if necessary.

A New Sum Function for Fixed-Point Variables

So here, in order to make the result unique in different orders, a new function fxsum is needed in this theme. The usage is similar to built-in sum:

fxsum(iterable, start=0)

fxsum requires an iterable parameter containing fixbv instances, and returns the sum as a fixbv variable. The width of the fractional part would be the longest in the iterable, while the width of the width of the integer part is the longest plus ceil(log(N, 2)), in which N is the width of the longest integer part.

Instead of using a + b + c + d, we could write

fxsum((a, b, c, d))

to avoid ambiguity of bit width in the result.

As a result, in the previous example, we could obtain that

>>> fxsum((a, b, c, d)).format
(21, 9, [11])

which is the optimal format in the worst case.

over 4 years ago

In the previous post, I thought the point alignment of fixbv should be implemented in a short time. Soon I found myself wrong.

After fixing a compatibility bug in the unit test test_fixbv.py, the problem of point alignment has been revealed.

If point alignment only solved in _fixbv.py, in the case of a variable of Signal plus or minus a variable of fixbv, MyHDL simulator will throw a problem. So I have to implement both in Signal.__add__ and fixbv.__add__. Also, I have to consider if they plus or minus intbv.

So, it causes a problem of cross import. I posted this StackOverflow question and you may see the implementation.

Temporarily, my solution is (in fixbv.__add__):

def __add__(self, other):
    if isinstance(other, fixbv):
        iW = self._W + other._W
        # Solve the case if `type(other) is Signal`
        return other.__radd__(self)

This implementation only considered fixbv and Signal. However, if I consider intbv, things might be more complex.

It leaves a question I am going to solve now.

over 4 years ago

Hello, people.

I should say "Long time no see" since I have not written blogs since about 2 or 3 weeks ago. Sorry for that.

The first evaluation is coming soon. I hope my blog would make it.

Related Source Code

I have opened MEP-111 branch on GitHub for this project. I also opened a PR to the GSoC branch in the upstream.

All the progress of this project can be checked in these problems.


The front end of fixbv (the part for MyHDL simulation usage) has been roughly implemented by cfelton. Although it has some issues, I may adopt it as a good start.

There are some issues in cfelton's implementation:

  1. At his time of implementation, MyHDL supports only Python 2.x. So the compatibility between 2.x and 3.x has not been considered.
  2. Compiler backend has not been implemented.
  3. In fixbv.__init__, round_mode and overflow_mode were not implemented.
  4. Point alignment has not been implemented.
  5. Many places marked as @todo. Most of them are small problems.

I planned to solve the above issues except 2 before the beginning of July.


Python 3.x Compatibility


Compiler Backend

Conversion is the core issue of this project. Before advancing into this phase, I must solve other issues first.

I am planning to do it in July and August.

Round Mode and Overflow Mode

To be decided.

Should it also be considered while implementing compiler backend?

Point Alignment

Almost finished.

Other Small Problems


Future Plans

In July, I will implement the compiler backend of fixbv.

In the first 2 weeks of July, I will write conversion tests for fixbv. After that, I will implement the backend of fixbv and write documents for it.

In mid August, this project should be finished.

over 4 years ago

To make full use of MyHDL, it is strongly recommended to install and configure cosimulation modules. So that the output (Verilog/VHDL code) of MyHDL compiler backend can be evaluated, and the current MyHDL code can work with the conventional code base and migrate into industrial EDA workflow.

At this time, only Verilog users have to install it for cosimulation. So that we can use from_myhdl and to_myhdl in our example.

Here, I assume that MyHDL has been installed by the steps in the previous post.

Installing Icarus Verilog

MyHDL supports several popular RTL simulators. At this time, Icarus Verilog, GHDL, cver, and ModelSim were supported. Here, Icarus Verilog will be used as an example.

Icarus Verilog is already in some Linux distributions. For example, in Ubuntu, type the following command to install Icarus Verilog:

sudo apt install iverilog

For Windows users, Windows binaries might be useful.

VPI module

VPI module provides an interface for Verilog to share data with MyHDL.

Compiling VPI module

In the source code of MyHDL, the cosimulation folder contains several types of simulator that supported. In each subfolder, the README file describes how to install MyHDL support for these simulators.

For Icarus Verilog, go to cosimulation/icarus, and then use command make to compile the vpi module. Then, you will get myhdl.vpi after compiling.

You can copy myhdl.vpi to other places while needed.

Check installation

cd myhdl/test/core
py.test test_Cosimulation.py

Follow cosimulation chapter of the manual could also make sense, especially when you want to get a direct message of why the cosimulation does not work.

Please refer to corresponding manpages to get the meanings of options for iverilog and vvp.

Notes on Windows Installation

The process of Windows installation is almost the same, but there are still some difference. Here are the notes that may help your installation.

  1. After installing iverilog, remember to add the path of iverilog.exe and iverilog-vpi.exe to the system path.

  2. Before compiling VPI files, a MinGW implementation should be installed and added into system path.

  3. When compiling VPI modules, myhdl.c will probably be overwritten and fail to compile. If this situation occurs, please replace myhdl.c with the original one, and then copy the vlog_startup_routines function in myhdl_table.c to the end of myhdl.c, and then execute

iverilog-vpi myhdl.c
over 4 years ago

The first step to get involved in the development MyHDL is to set up the environment. In this blog post, I will introduce the development environment.

Before reading these files, I assume you should have a basic knowledge of Python, Git, and MyHDL.

Step 0: Prerequisites

The software prerequisites of developing MyHDL is quite simple: Python and Git. If you want to do cosimulation, you may need a VHDL/Verilog simulator supported by MyHDL.

There are bunches of tutorials on the Internet that teach you how to install Python and Git. You can search them for detailed instructions of installation. Here is just a reminder.

Also, a GitHub account is required.


For MyHDL development, the version of Python should be at least 2.6 for 2.x versions and at least 3.4 for Python 3.x. Personally, I prefer Python 3.x, but it doesn't matter which version you use for developing. For compatibility purpose, the code should treat 2.x and 3.x equally.

On Linux distributions, you can install from either package repositories or source code.

For example, you may execute the following command on Ubuntu to install from software repo:

sudo apt install python3

On Windows, you can download directly from the official website of Python. Or you can also download Python distributions such as Anaconda since it contains more tools especially for science and Engineering.


On Linux distributions, you can install git from software repository. For example, in Ubuntu, you may execute the following command:

sudo apt install git

For Windows and Mac users, GitHub Desktop is recommended.

You may find the book Pro Git useful when you using git command line.

My blog posts will mainly introduce operations in command line. If you prefer GitHub Desktop, you may find the user interface very friendly and very easy to do equivalent work as in command line. But the git command line is more extensive.

Register and configure GitHub account

Please follow the related documents in GitHub Help.

Also, it is nice to configure git by following the related section of Pro Git. Especially, configuring text editor using in git is recommended.

Step 1: Fork the repo

The main repository of MyHDL is on GitHub.

In MyHDL repository page, click "Fork" button, which is located on the upper right corner of the page. Then, the page will jump to your own repository forked before.

Forking a repo on GitHub is like to create your own copy of the project. You may make changes on your own repo and make pull requests to contribute back to the original project.

Step 2: Clone the repo to your own computer

Now, you are in your own repo.

Click "Clone or download" button, and the URL will pop up. Copy this URL to the clipboard.

In the working directory of your local computer, execute the following command: (Please change "qrqiuren" to your own user name)

git clone https://github.com/qrqiuren/myhdl.git

Then, you will see a subfolder called myhdl is created. It is your git repository.

Execute the following command to change the current directory to myhdl subfolder.

cd myhdl

Add the upstream as a remote would be easier to keep up with upstream in later development.

git remote add upstream https://github.com/myhdl/myhdl.git
git fetch upstream

Step 3: Setup MyHDL in developer mode

You may need setuptools to setup in developer mode. In Ubuntu, install setuptools by

sudo apt install python-setuptools

Execute the following command to setup MyHDL in developer mode

sudo python setup.py develop

If you have read the README file, you may find that I have replaced install into develop in the command. That is because if I use develop, and changed the code in myhdl directory, the changes will immediately affect other Python programs that import myhdl. This is very useful when you change the code frequently.

Installation of cosimulation modules

Please refer to GSoC #2: Configuration of Cosimulation.

Run unit tests

cd myhdl/test/core

It will start running unit tests. If there is any problems, maybe the installation or the code is not correct.

Step 4: Contribute

In order not to mix with other code, you should work on a new branch instead of master. For example, if you want to add a branch to implement MEP-111 specified in the website of MyHDL, you should switch to this branch by using

git branch mep-111
git checkout mep-111

And then, you can make the commits while remaining the main branch unaffected.

Now, it is time to review the code and contribute to it.

After you make some changes in the code, you may have to commit it. Execute the following command to see what you have done since last commit

git status

Now, you can add the changed files to the buffer to prepare for the next commit. For example, if you changed some code in myhdl/_intbv.py, you may execute

git add myhdl/_intbv.py

After you added all the files, you can commit to the git database by using

git commit

Then, a text editor (as configured before) will be opened for you to edit the commit message. You should briefly write what you have done in this commit, preferrably in one sentence. Please note that usually commits in git should be as "atomic" as possible, which means one commit does only one thing and cannot be separated anymore.

Finally, if you want to push the local git database to GitHub, you should execute the following:

git push origin mep-111

Writing unit tests

It is required to write corresponding unit tests if you modified the code. You may put your unit tests to the corresponding subfolder in myhdl/test/.

Please refer to Writing Tests for a guide to write unit tests for MyHDL.

Step 5: Make a pull request

A pull request (PR) is a way to request contributing the code from your repo to the original repo.

Before making a PR

Here is a simple checklist before making a PR:

  • Have you committed all the code that consist this PR?
  • Have you written unit tests for it?

Make a pull request

Go to the GitHub page of the forked repository.

Click "New pull request" button, and then submit the information about this pull request. Then, the PR will be created.

For more information of making a pull request, please refer to GitHub help.

over 4 years ago

Hello, people.

It is exciting to announce that my proposal MyHDL: Fixed-point Compiler has been accepted as a project of Google Summer of Code.

From now on, I will start blogging in English on this site. I will write about the progress of my project and studies on MyHDL. For progress, please checkout the GSoC tag of this blog. Related code will be on my GitHub repo.

Code speaks louder than words. Then, let's get started.