Wednesday, May 28, 2014

Wednesday Night Hack #1

This week, I began the development of basic a front end tool to wrap the functionality of the CAD tools bundled with my LatticeECP3 FPGA development board.  The packaged tools are Aldec-HDL, Synplify Pro, and Lattice Diamond (the FPGA implementation tool).  Active-HDL, by all respects, have a pretty solid GUI, but my preference is to maintain control from the command line.

Rather than implement a custom Makefile library or a traditional Perl script, I chose to develop an API using Python that a user could use to build up their own flow apps.  The idea is to replace configuration files or application-specific dynamic scripting languages with an actual scripting language.  This approach scales better over time.

The first step to this process is project management. We want the ability to manage groups of files.  I call these groups of files components.  Examples of components include RTL unit (Verilog module and its sub modules), UVC (Universal Verification Component), design IP library (RAM library, FPGA megafunctions), etc.

We also want the ability to establish "depends on" relationships between components.  Here is an example component definition written in Python

c.set_name('top')
c.add_file('top.v')
c.add_require('led')
c.add_require('ram')

For a simple project consisting of three components. Top, RAM, and LED where top depends on RAM and LED and LED also depends on RAM.  The flist would need to resemble following.  The script below is able to produce this.  In fact, below is the actual output of the script.

# Component: ram
/cygdrive/d/Projects/system/rtl/ram/ram.v

# Component: led
# Requires: ram
/cygdrive/d/Projects/system/rtl/led/led.v

# Component: top
# Requires: led,ram
/cygdrive/d/Projects/system/rtl/top/top.v

I achieve this functionality by implementing two classes in Python: Component and Go.  Here are some code snippets.  First, I build a tree data structure in process_requires function.  I traverse the tree in function get_flist using a simple recursive algorithm.

Here is a snippet of the source code.  There's no error checking, so YMMV.

class Component:
    """ Exposed to user """
    def add_file(self, file):
        self.files.append(self.root_dir+'/'+file)

    """ Exposed to user """
    def add_option(self, option):
        self.options.append(option)

    """ Exposed to user """
    def add_require(self, require):
        self.requires.append(require)

    def get_flist(self):
        flist = '#' * 80 + '\n' # 80 character comment line
        flist +=  '# Component: ' + self.get_name() + '\n'
        if self.requires:
            flist +=  '# Requires: '+','.join(self.requires)+'\n'
        if self.files:
            flist +=  '\n'.join(self.files) + '\n'
        if self.options:
            flist +=  '\n'.join(self.options) + '\n'
        return flist

class Go:
    """ Load all component under root_dir """
    def load_comps(self):
        for root,dirs,files in os.walk(self.root_dir):
            for f in files:
                if f == 'comp.py':
                    c = Component()
                    c.set_root_dir(root)
                    execfile(root+'/'+f)
                    self.component[c.get_name()] = c

    def process_requires(self,node):
        for require in node.requires:
            child_node = self.component[require]
            self.process_requires(child_node)
            node.child_nodes.append(child_node)

    def init(self):
        self.load_comps()
        self.process_requires(self.get_top_node())

    def get_flist(self):
        self.visited = {}
        self.do_get_flist(self.get_top_node())

    """ Traverse tree using recursive post-order search """
    def do_get_flist(self,node):
        for child in node.child_nodes:
            if not child.get_name() in self.visited:
                self.do_get_flist(child)
        if not node.get_name() in self.visited:
           print(node.get_flist())
           self.visited[node.get_name()] = 1 # mark visited