Exemplo n.º 1
0
    def build(cls, top_verilog_file, verilog_path = [], build_dir = None, json_data = None, gen_only = False,
              trace_depth=2, top_module_name=None, auto_eval=True, read_internal_signals=False, extra_args=[]):
        """ Build an object file from verilog and load it into python.

        Creates a folder build_dir in which it puts all the files necessary to create
        a model of top_verilog_file using verilator and the C compiler. All the files are created in build_dir.
        If build_dir is None, a unique temporary directory will be created.

        If the project is made of more than one verilog file, all the files used by the top_verilog_file will be searched
        for in the verilog_path list.

        json_data is a payload than can be used to add a json as a string in the object file compiled by verilator.

        This allow to keep the object file a standalone model even when extra information iis useful to describe the
        model.

        For example a model coming from bluespec will carry the list of rules of the model in this payload.

        gen_only stops the process before compiling the cpp into object.
        """
        # verilator can't find memory files unless they are in the cwd
        # so switch to where the top verilog is
        # (assuming the mem .dat files are also there...)
        if not isinstance(top_verilog_file, list):
            top_verilog_dir = os.path.dirname(os.path.realpath(top_verilog_file))
        else:
            top_verilog_dir = verilog_path[0]


        old_cwd = os.getcwd()
        os.chdir(top_verilog_dir)

        # get the module name from the verilog file name
        if not isinstance(top_verilog_file, list):
            top_verilog_file_base = os.path.basename(top_verilog_file)
            verilog_module_name, extension = os.path.splitext(top_verilog_file_base)
        else:
            verilog_module_name = top_module_name
            extension = None

        builddir_is_tmp = False
        if build_dir is None:
            build_dir = tempfile.mkdtemp(prefix=verilog_module_name+"-")
            builddir_is_tmp = True
        if top_module_name is None and extension != '.v':
            raise ValueError('PyVerilator() expects top_verilog_file to be a verilog file ending in .v')

        # prepare the path for the C++ wrapper file, ensure no old files remain
        # to avoid errors due to recompiling same module with different config
        if os.path.exists(build_dir):
            shutil.rmtree(build_dir)
        os.makedirs(build_dir)
        verilator_cpp_wrapper_path = os.path.join(build_dir, 'pyverilator_wrapper.cpp')

        # call verilator executable to generate the verilator C++ files
        verilog_path_args = []
        for verilog_dir in verilog_path:
            verilog_path_args += ['-y', verilog_dir]

        if top_module_name is not None:
            top_module_arg = ["--top-module",top_module_name]
        else:
            top_module_arg = []

        if not isinstance(top_verilog_file, list):
            verilog_file_arg = [top_verilog_file]
        else:
            verilog_file_arg = top_verilog_file

        # Verilator is a perl program that is run as an executable
        # Old versions of Verilator are interpreted as a perl script by the shell,
        # while more recent versions are interpreted as a bash script that calls perl on itself
        which_verilator = shutil.which('verilator')
        if which_verilator is None:
            raise Exception("'verilator' executable not found")
        # tracing (--trace) is required in order to see internal signals
        verilator_args = ['perl', which_verilator, '-Wno-fatal', '-Mdir', build_dir] \
                         + verilog_path_args \
                         + ['--CFLAGS',
                           '-fPIC --std=c++11',
                            '--trace',
                            '--trace-depth', '%d' % trace_depth,
                            '--cc'] \
                         + verilog_file_arg \
                         + top_module_arg \
                         + ['--exe',
                            verilator_cpp_wrapper_path] \
                         + extra_args
        launch_process_helper(verilator_args)

        # get inputs, outputs, and internal signals by parsing the generated verilator output
        inputs = []
        outputs = []
        internal_signals = []
        verilator_h_file = os.path.join(build_dir, 'V' + verilog_module_name + '.h')

        def search_for_signal_decl(signal_type, line):
            # looks for VL_IN*, VL_OUT*, or VL_SIG* macros
            result = re.search('(VL_' + signal_type + r'[^(]*)\(([^,]+),([0-9]+),([0-9]+)(?:,[0-9]+)?\);', line)
            if result:
                signal_name = result.group(2)
                if signal_type == 'SIG':
                    if '[' not in signal_name and int(result.group(4)) == 0:
                        # this is an internal signal
                        signal_width = int(result.group(3)) - int(result.group(4)) + 1
                        return (signal_name, signal_width)
                    else:
                        return None
                else:
                    # this is an input or an output
                    signal_width = int(result.group(3)) - int(result.group(4)) + 1
                    return (signal_name, signal_width)
            else:
                return None

        with open(verilator_h_file) as f:
            for line in f:
                result = search_for_signal_decl('IN', line)
                if result:
                    inputs.append(result)
                result = search_for_signal_decl('OUT', line)
                if result:
                    outputs.append(result)
                result = search_for_signal_decl('SIG', line)
                if result and read_internal_signals:
                    internal_signals.append(result)

        # generate the C++ wrapper file
        verilator_cpp_wrapper_code = template_cpp.template_cpp(verilog_module_name, inputs, outputs, internal_signals,
                                                               json.dumps(json.dumps(json_data)))
        with open(verilator_cpp_wrapper_path, 'w') as f:
            f.write(verilator_cpp_wrapper_code)

        # if only generating verilator C++ files, stop here
        if gen_only:
            os.chdir(old_cwd)
            return None

        # call make to build the pyverilator shared object
        make_args = ['make', '-C', build_dir, '-f', 'V%s.mk' % verilog_module_name, 'CFLAGS=-fPIC -shared',
                     'LDFLAGS=-fPIC -shared']
        launch_process_helper(make_args)
        so_file = os.path.join(build_dir, 'V' + verilog_module_name)
        if builddir_is_tmp:
            # mark the build dir for removal upon destruction
            ret = cls(so_file, builddir_to_remove = build_dir, auto_eval = auto_eval)
        else:
            ret = cls(so_file, auto_eval = auto_eval)
        os.chdir(old_cwd)
        return ret
Exemplo n.º 2
0
    def build(cls,
              top_verilog_file,
              verilog_path=[],
              build_dir='obj_dir',
              json_data=None,
              gen_only=False,
              quiet=False,
              command_args=(),
              verilog_defines=(),
              params={},
              verilator_args=(),
              extra_cflags="",
              verilog_module_name=None):
        """Build an object file from verilog and load it into python.

        Creates a folder build_dir in which it puts all the files necessary to create
        a model of top_verilog_file using verilator and the C compiler. All the files are created in build_dir.

        If the project is made of more than one verilog file, all the files used by the top_verilog_file will be searched
        for in the verilog_path list.

        json_data is a payload than can be used to add a json as a string in the object file compiled by verilator.

        This allow to keep the object file a standalone model even when extra information iis useful to describe the
        model.

        For example a model coming from bluespec will carry the list of rules of the model in this payload.

        gen_only stops the process before compiling the cpp into object.

        ``quiet`` hides the output of Verilator and its Makefiles while generating and compiling the C++ model.

        ``command_args`` is passed to Verilator as its argv.  It can be used to pass arguments to the $test$plusargs and $value$plusargs system tasks.

        ``verilog_defines`` is a list of preprocessor defines; each entry should be a string, and defined macros with value should be specified as "MACRO=value".

        ``params`` is a dictionary of parameter-overrides for the top-level module; each key should be a string and values can be an integer or a string.

        ``verilator_args`` is a list of extra arguments to pass to verilator

        ``extra_cflags`` is a string of extra args to set as CFLAGS in verilator. These will be used to compile the verilated C++

        If compilation fails, this function raises a ``subprocess.CalledProcessError``.
        """
        # some simple type checking to look for easy errors to make that can be hard to debug
        if isinstance(verilog_defines, str):
            raise TypeError('verilog_defines expects a list of strings')
        if isinstance(command_args, str):
            raise TypeError('command_args expects a list of strings')

        # get the module name from the verilog file name
        top_verilog_file_base = os.path.basename(top_verilog_file)

        if verilog_module_name is None:
            verilog_module_name, extension = os.path.splitext(
                top_verilog_file_base)
        else:
            _, extension = os.path.splitext(top_verilog_file_base)

        if extension not in ['.v', '.sv']:
            raise ValueError(
                'PyVerilator() expects top_verilog_file to be a verilog file ending in .v or .sv'
            )

        # prepare the path for the C++ wrapper file
        if not os.path.exists(build_dir):
            os.makedirs(build_dir)
        verilator_cpp_wrapper_path = os.path.join(build_dir,
                                                  'pyverilator_wrapper.cpp')

        # call verilator executable to generate the verilator C++ files
        verilog_path_args = []
        for verilog_dir in verilog_path:
            verilog_path_args += ['-y', verilog_dir]

        # Convert params into command-line flags
        param_args = []
        for param, value in params.items():
            # Strings must be quoted
            if isinstance(value, str):
                value = "\"{}\"".format(value)

            param_args.append("-G{}={}".format(param, value))

        # Verilator is a perl program that is run as an executable
        # Old versions of Verilator are interpreted as a perl script by the shell,
        # while more recent versions are interpreted as a bash script that calls perl on itself
        which_verilator = shutil.which('verilator')
        if which_verilator is None:
            raise Exception("'verilator' executable not found")
        verilog_defines = ["+define+" + x for x in verilog_defines]
        # tracing (--trace) is required in order to see internal signals
        verilator_args = ['perl', which_verilator, '-Wno-fatal', '-Mdir', build_dir] \
                         + verilog_path_args \
                         + verilog_defines \
                         + param_args \
                         + verilator_args \
                         + ['-CFLAGS',
                           '-fPIC -shared --std=c++11 -DVL_USER_FINISH ' + extra_cflags,
                            '--trace',
                            '--cc',
                            top_verilog_file,
                            '--exe',
                            verilator_cpp_wrapper_path]
        call_process(verilator_args)

        # get inputs, outputs, and internal signals by parsing the generated verilator output
        inputs = []
        outputs = []
        internal_signals = []
        verilator_h_file = os.path.join(build_dir,
                                        'V' + verilog_module_name + '.h')

        def search_for_signal_decl(signal_type, line):
            # looks for VL_IN*, VL_OUT*, or VL_SIG* macros
            result = re.search(
                '(VL_' + signal_type +
                r'[^(]*)\(([^,]+),([0-9]+),([0-9]+)(?:,[0-9]+)?\);', line)
            if result:
                signal_name = result.group(2)
                if signal_type == 'SIG':
                    if signal_name.startswith(
                            verilog_module_name
                    ) and '[' not in signal_name and int(result.group(4)) == 0:
                        # this is an internal signal
                        signal_width = int(result.group(3)) - int(
                            result.group(4)) + 1
                        return (signal_name, signal_width)
                    else:
                        return None
                else:
                    # this is an input or an output
                    signal_width = int(result.group(3)) - int(
                        result.group(4)) + 1
                    return (signal_name, signal_width)
            else:
                return None

        with open(verilator_h_file) as f:
            for line in f:
                result = search_for_signal_decl('IN', line)
                if result:
                    inputs.append(result)
                result = search_for_signal_decl('OUT', line)
                if result:
                    outputs.append(result)
                result = search_for_signal_decl('SIG', line)
                if result:
                    internal_signals.append(result)

        # generate the C++ wrapper file
        verilator_cpp_wrapper_code = template_cpp.template_cpp(
            verilog_module_name, inputs, outputs, internal_signals,
            json.dumps(json.dumps(json_data)))
        with open(verilator_cpp_wrapper_path, 'w') as f:
            f.write(verilator_cpp_wrapper_code)

        # if only generating verilator C++ files, stop here
        if gen_only:
            return None

        # call make to build the pyverilator shared object
        make_args = [
            'make', '-C', build_dir, '-f',
            'V%s.mk' % verilog_module_name, 'LDFLAGS=-fPIC -shared'
        ]
        call_process(make_args, quiet=quiet)
        so_file = os.path.join(build_dir, 'V' + verilog_module_name)
        return cls(so_file, command_args=command_args)