Thursday, June 2, 2016

FuseSoC and your custom workflow

Last week a colleague made a horrible mistake. He casually asked me to tell him a bit about FuseSoC. This made me very happy and after an hour he probably regretted asking (sorry Johan).

Anyway, it turned out that he wanted to know if FuseSoC was the right tool for him. As most other RTL developers, he had his own set of custom scripts to launch simulations - in this case it was Makefiles. After discussing a bit back and forth we both realized that switching to a complete FuseSoC-based workflow wouldn't really be all that great for him, as he would lose some of the tight tool-integration with his existing scripts. Talking a bit more however revealed that he had no great solution for bringing in all the source files that his makefiles should digest, and as I have written about before, FuseSoC is not only meant as a end-to-end solution for running simulations or making FPGA images. It's also meant to be used as a library so that it can be hooked up to other tools. And what my colleague needed in this case was mainly a list of all RTL source files, so I said that I could make such a tool based on FuseSoC in fifteen minutes. It turned that I was wrong. Starting out with the script I did for VUnit integration, it took less than ten minutes to get it working. Without further ado, I therefore present the FuseSoC file dumper script as an example on how to use just the parts of FuseSoC that brings in dependencies, sorts them in order and lists all files, as long as your cores use the FuseSoC .core format to describe them. Use it, adapt it and share it.


#
# FuseSoC source file dumper. fusesoc_file_dump_demo
#
# Copyright (C) 2016  Olof Kindgren <olof.kindgren@gmail.com>
#
# Permission to use, copy, modify, and/or distribute this software for any
# purpose with or without fee is hereby granted, provided that the above
# copyright notice and this permission notice appear in all copies.
#
# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.

from collections import OrderedDict
import os.path
import sys

from fusesoc.config import Config
from fusesoc.coremanager import CoreManager, DependencyError

if len(sys.argv) != 2:
    print("Usage: {} <top level core>".format(sys.argv[0]))
    exit(1)
top_core = sys.argv[1]

#Create singleton instances for core manager and configuration handler
#Configuration manager is not needed in this example
cm = CoreManager()
config = Config()

#Add core libraries that were picked up from fusesoc.conf by the config handler
try:
    cm.add_cores_root(config.cores_root)
except (RuntimeError, IOError) as e:
    pr_warn("Failed to register cores root '{}'".format(str(e)))

#Get the sorted list of dependencies starting from the top-level core
try:
    cores = cm.get_depends(top_core)
except DependencyError as e:
    print("'{}' or any of its dependencies requires '{}', but this core was not found".format(top_core, e.value))
    exit(1)

#Iterate over cores, filesets and files and add all relevant sources files to a list
incdirs = set()
libraries = []
src_files = []

#'usage' is a list of tags to look for in the filesets. Only look at filesets where any of these tags are present
usage = ['sim']
for core_name in cores:
    core = cm.get_core(core_name)
    core.setup()
    basepath = core.files_root
    for fs in core.file_sets:
        if (set(fs.usage) & set(usage)) and ((core_name == top_core) or not fs.private):
            for file in fs.file:
                if file.is_include_file:
                    incdirs.add(os.path.join(basepath, os.path.dirname(file.name)))
                else:
                    libraries.append(file.logical_name)
                    src_files.append("{},{},{}".format(os.path.join(basepath, file.name),
                                                    file.logical_name,
                                                    file.file_type))

print("==Include directories==")
print('\n'.join(incdirs))
print("==Libraries==")
print('\n'.join(list(OrderedDict.fromkeys(libraries))))
print("==Source files==")
print('\n'.join(src_files))

Granted that you have a fusesoc.conf file in your current directory or ~/.config/fusesoc and the FuseSoC standard core library installed, both of which you get if you install FuseSoC, you can now run python fusesoc_file_dump_demo.py de0_nano to get a list of all source files, include directories and libraries needed to build the de0_nano system.