Py4Hw User Guide> 11.1 HLS of combinational circuits¶

High Level Synthesis in py4hw refers to the process of creating hardware from python source code. The result of this process is not a Verilog code, but a py4hw structural model, which can then be translated to verilog.

Thus, this is a source-to-source transformation in the python language domain.

Let's create this circuit to compute the square root of an integer value using the babylonian method. Note that is is a combinational circuit, because all the behavior takes place in the propagate method.

In [1]:
import py4hw
In [2]:
class SquareRoot(py4hw.Logic):
    def __init__(self, parent, name, a, r):
        super().__init__(parent, name)
        
        self.a = self.addIn('a', a)
        self.r = self.addOut('r', r)
        
        
    def propagate(self):
        maxv = 1 << self.a.getWidth()
        maxiter = int(math.log2(math.log2(maxv))) + 4
        #print('max iters:', maxiter)
        
        n = self.a.get() 
        if n == 0:
            r = 0
        elif n == 1:
            r = 1
        else:
            guess = n
            done = False
            for _ in range(maxiter):
                # For a combinational circuit loops must be fully unrollable,
                # otherwise no synthesys is possible
                if (done):
                    pass
                else:
                    next_guess = (guess + (n // guess)) // 2  # Integer division

                    if next_guess >= guess:  # Convergence condition: guess stops decreasing
                        done = True
                    else:
                        guess = next_guess
            r = guess
            
        self.r.put(r)

we want to create an structural circuit from this behavioral description. This is what we expect from High Level Synthesis workflows. However, typical HLS workflows will create Verilog from high-level language. We can follow a different approach, generate a py4hw structural model (in Python) from a py4hw behavioral model (also in Python, of course).

In [3]:
from py4hw.transpilation.python2structural import Python2StructuralCode
In [4]:
coder = Python2StructuralCode(SquareRoot)

print(coder.getCode())
---------------------------------------------------------------------------
OSError                                   Traceback (most recent call last)
Cell In[4], line 3
      1 coder = Python2StructuralCode(SquareRoot)
----> 3 print(coder.getCode())

File C:\Projects\Research\INT_Py4hw\py4hw\py4hw\transpilation\python2structural.py:20, in Python2StructuralCode.getCode(self)
     17 def getCode(self):
     18     ret =  '# This file was automatically created by Python2StructuralCode\n'
---> 20     class_ast = get_class_ast(self.clazz)
     22     renamer = ClassRenamer(self.clazz.__name__, self.clazz.__name__ + 'Structural')
     23     class_ast = renamer.visit(class_ast)

File C:\Projects\Research\INT_Py4hw\py4hw\py4hw\transpilation\astutils.py:103, in get_class_ast(clazz)
    100     raise ValueError("Input must be a class")
    102 # Get the full class source and parse it
--> 103 source = textwrap.dedent(inspect.getsource(clazz))
    104 module_node = ast.parse(source)
    106 # Find the class definition node

File ~\AppData\Local\anaconda3\Lib\inspect.py:1262, in getsource(object)
   1256 def getsource(object):
   1257     """Return the text of the source code for an object.
   1258 
   1259     The argument may be a module, class, method, function, traceback, frame,
   1260     or code object.  The source code is returned as a single string.  An
   1261     OSError is raised if the source code cannot be retrieved."""
-> 1262     lines, lnum = getsourcelines(object)
   1263     return ''.join(lines)

File ~\AppData\Local\anaconda3\Lib\inspect.py:1244, in getsourcelines(object)
   1236 """Return a list of source lines and starting line number for an object.
   1237 
   1238 The argument may be a module, class, method, function, traceback, frame,
   (...)
   1241 original source file the first line of code was found.  An OSError is
   1242 raised if the source code cannot be retrieved."""
   1243 object = unwrap(object)
-> 1244 lines, lnum = findsource(object)
   1246 if istraceback(object):
   1247     object = object.tb_frame

File ~\AppData\Local\anaconda3\Lib\inspect.py:1063, in findsource(object)
   1055 def findsource(object):
   1056     """Return the entire source file and starting line number for an object.
   1057 
   1058     The argument may be a module, class, method, function, traceback, frame,
   1059     or code object.  The source code is returned as a list of all the lines
   1060     in the file and the line number indexes a line in that list.  An OSError
   1061     is raised if the source code cannot be retrieved."""
-> 1063     file = getsourcefile(object)
   1064     if file:
   1065         # Invalidate cache if needed.
   1066         linecache.checkcache(file)

File ~\AppData\Local\anaconda3\Lib\inspect.py:940, in getsourcefile(object)
    936 def getsourcefile(object):
    937     """Return the filename that can be used to locate an object's source.
    938     Return None if no way can be identified to get the source.
    939     """
--> 940     filename = getfile(object)
    941     all_bytecode_suffixes = importlib.machinery.DEBUG_BYTECODE_SUFFIXES[:]
    942     all_bytecode_suffixes += importlib.machinery.OPTIMIZED_BYTECODE_SUFFIXES[:]

File ~\AppData\Local\anaconda3\Lib\inspect.py:908, in getfile(object)
    906             return module.__file__
    907         if object.__module__ == '__main__':
--> 908             raise OSError('source code not available')
    909     raise TypeError('{!r} is a built-in class'.format(object))
    910 if ismethod(object):

OSError: source code not available
In [5]:
coder.clazz.__name__
Out[5]:
'SquareRoot'
In [ ]: