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
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)