Thursday, June 12, 2014

Wednesday Night Hack #3 - BARF is done

I am done working on my Python-based build and run flow.

While doing this hack, I took a small detour and evaluated SCons for use with EDA tools.  I was successful in understanding the declarative nature of the tool and even wrote some custom builders, however, it quickly became clear that I was trying to fit a square peg into a round hole and abandoned the effort.  I was hoping that I could adopt a tried-and-tested solution and would love to hear from others who have been successful, if any.

The feature list for BARF is limited.  It has the ability to collect groups of files into components and provides a wrapper to execute commands.

Each component is specified using a YAML file.  The component contains a list of files, options, and requires.  Requires are needed to indicate parent-child relationship between components.  For example, a block may require a ram component or the chip-level wrapper requires all block components.
name: led
files: [led.v]
options: []
requires: [ram]
I will point out that the use of YAML is a reversal from the previous blog post where I experienced with making the mechanism to declare a component be a Python script itself.

I kept the API for execute commands extremely straight forward.  Each custom job inherits from a base class.  Since each job is modeled as a Python object, artefacts from each job can be collected by the specialized class and the collected Python can easily passed from one job to another.

I am pasting the relevant source code below.  Maybe next week, I'll set up a GitHub account.
class Barf(object):
    """ Build and Run Flow """

    def post_order(self, node):
        """ Recursive post-order tree traversal """
        if not node:
            return
        for child_name in node['requires']:
            child_node = self.comp[child_name]
            self.post_order(child_node)
        if node['visited'] == 0:
            self.flist_obj.append(node)
            node['visited'] = 1

    def load_comps(self, top_node):
        """ Load components from yaml files """
        self.comp = {}

        for root,dirs,files in os.walk(os.environ.get('WS')):
            for file in files:
                if file == "comp.yml":
                    full_path = os.path.join(root, file)
                    stream = yaml.load(open(full_path))

                    # use list comprehension (!)
                    stream['files'] = [ root+'/'+x for x in stream['files'] ]

                    name = stream['name']
                    self.comp[name] = {}
                    self.comp[name]['files'] = stream['files']
                    self.comp[name]['options'] = stream['options']
                    self.comp[name]['requires'] = stream['requires']
                    self.comp[name]['visited'] = 0

        self.flist_obj = []
        self.post_order(self.comp[top_node])
  
class Job(object):
    """ Base class for job object """

    def exec_cmd(self,cmd,wdir=os.environ.get('WSTMP')):
        """ Execute shell command """
        p = subprocess.Popen('cd {0} && {1}'.format(wdir,cmd),stdout=subprocess.PIPE,shell=True)
        (stdout, stderr) = p.communicate()
        if p.returncode != 0: raise Exception("Command {0} failed ".format(cmd))
        return (stdout, stderr)

    def cyg_to_win_path(self,cyg_path):
        """ Convert cygwin path to windows path """
        p = subprocess.Popen('cygpath -w '+cyg_path,stdout=subprocess.PIPE,shell=True)
        return '"'+p.communicate()[0].rstrip()+'"' #--HACK: cygwin

class CleanTmp(Job):
    def execute(self,lib_name='work'):
        self.exec_cmd('rm -rf {0}/*'.format(os.environ.get('WSTMP')))

class RunVlib(Job):
    def execute(self,lib_name='work'):
        self.exec_cmd('vlib {0}'.format(lib_name))

class RunVlog(Job):
    def execute(self,flist_obj):
        files = []
        for obj in flist_obj:
            files +=  obj['files']
        files = [ self.cyg_to_win_path(x) for x in files ]

        options = []
        for obj in flist_obj:
            options +=  obj['options']

        self.exec_cmd('vlog -sv2k5 {0} {1}'.format(' '.join(files),
                                                   ' '.join(options)))