def check_valid(s, m, cfg, irepr): pmap, src, flist, include = \ cfg.port_map, cfg.src_file, cfg.v_flist, cfg.v_include # Check params for param_name, value in cfg.params.items(): if not isinstance(value, int): raise InvalidPassOptionValue( "params", cfg.params, cfg.Pass.__name__, f"non-integer parameter {param_name} is not supported yet!" ) # Check port map # TODO: this should be based on RTLIR for port in pmap.keys(): if port.get_host_component() is not m: raise InvalidPassOptionValue( "port_map", pmap, cfg.Pass.__name__, f"Port {port} does not exist in component {irepr.get_name()}!" ) # Check src_file if cfg.src_file and not os.path.isfile(expand(cfg.src_file)): raise InvalidPassOptionValue("src_file", cfg.src_file, cfg.Pass.__name__, 'src_file should be a file path!') if cfg.v_flist: raise InvalidPassOptionValue( "v_flist", cfg.v_flist, cfg.Pass.__name__, 'Placeholders backed by Verilog flist are not supported yet!') # Check v_flist if cfg.v_flist and not os.path.isfile(expand(cfg.v_flist)): raise InvalidPassOptionValue("v_flist", cfg.v_flist, cfg.Pass.__name__, 'v_flist should be a file path!') # Check v_include if cfg.v_include: for include in cfg.v_include: if not os.path.isdir(expand(include)): raise InvalidPassOptionValue( "v_include", cfg.v_include, cfg.Pass.__name__, 'v_include should be an array of dir paths!') # Check if the top module name appears in the file if cfg.src_file: found = False with open(cfg.src_file) as src_file: for line in src_file.readlines(): if cfg.top_module in line: found = True break if not found: raise InvalidPassOptionValue( "top_module", cfg.top_module, cfg.Pass.__name__, f'cannot find top module {cfg.top_module} in source file {cfg.src_file}.\n' f'Please make sure you have specified the correct top module name through ' f'the VerilogPlaceholderPass.top_module pass data name!')
def check( s ): super().check() # Exactly one of src_file and v_flist should be non-empty if (s.v_flist and os.path.isfile(expand(s.src_file))) or \ (not s.v_flist and not os.path.isfile(expand(s.src_file))): raise InvalidPassOptionValue( 'src_file', s.src_file, s.Pass.__name__, 'exactly one of src_file and v_flist should be non-emtpy!' )
def check_valid(s, m, cfg, irepr): pmap, src, flist, include = \ cfg.port_map, cfg.src_file, cfg.v_flist, cfg.v_include # Check params for param_name, value in cfg.params.items(): if not isinstance(value, int): raise InvalidPassOptionValue( "params", cfg.params, cfg.PassName, f"non-integer parameter {param_name} is not supported yet!" ) # Check port map # TODO: this should be based on RTLIR # unmapped_unpacked_ports = gen_unpacked_ports( irepr ) # unmapped_port_names = list(map(lambda x: x[0], list(unmapped_unpacked_ports))) # for name in pmap.keys(): # if name not in unmapped_port_names: # raise InvalidPassOptionValue("port_map", pmap, cfg.PassName, # f"Port {name} does not exist in component {irepr.get_name()}!") for name in pmap.keys(): try: eval(f'm.{name}') except: raise InvalidPassOptionValue( "port_map", pmap, cfg.PassName, f"Port {name} does not exist in component {irepr.get_name()}!" ) # Check src_file if cfg.src_file and not os.path.isfile(expand(cfg.src_file)): raise InvalidPassOptionValue("src_file", cfg.src_file, cfg.PassName, 'src_file should be a file path!') if cfg.v_flist: raise InvalidPassOptionValue( "v_flist", cfg.v_flist, cfg.PassName, 'Placeholders backed by Verilog flist are not supported yet!') # Check v_flist if cfg.v_flist and not os.path.isfile(expand(cfg.v_flist)): raise InvalidPassOptionValue("v_flist", cfg.v_flist, cfg.PassName, 'v_flist should be a file path!') # Check v_include if cfg.v_include: for include in cfg.v_include: if not os.path.isdir(expand(include)): raise InvalidPassOptionValue( "v_include", cfg.v_include, cfg.PassName, 'v_include should be an array of dir paths!')
def create_cc_cmd(s): c_flags = "-O0 -fPIC -fno-gnu-unique -shared" + \ ("" if s.is_default("c_flags") else f" {expand(s.c_flags)}") c_include_path = " ".join("-I" + p for p in s._get_all_includes() if p) out_file = s.get_shared_lib_path() c_src_files = " ".join(s._get_c_src_files()) ld_flags = expand(s.ld_flags) ld_libs = s.ld_libs coverage = "-DVM_COVERAGE" if s.vl_coverage or \ s.vl_line_coverage or \ s.vl_toggle_coverage else "" return f"g++ {c_flags} {c_include_path} {ld_flags}"\ f" -o {out_file} {c_src_files} {ld_libs} {coverage}"
def create_cc_cmd(s): # Shunning: GCC has a family of optimizations on the SSA tree -ftree-***. # It belongs to -O1, and takes a lot of time to execute when I compile the verilated C++. # However, the compiled code has worse performance after applying those tree optimizations. # I guess it's because verilator already emits very regular and low-level code in the form of C++. # Also gcc -O1, -O2, -O3 result in the same compilation time after all optimization flags are turned off # gcc -O0 still has shorter compilation time than O1-3 with all flags off # My guess is that -O1 has some hidden optimization that the user cannot turn off, and -O2-3 inherit that part # the difference between O1-3 is just flags # so right now I'm using "-O1 with all options off" to get the fastest compilation time and good performance # !!! -fno-tree-forwprop is removed due to: # In file included from /usr/include/fcntl.h:290:0, # from /usr/local/share/verilator/include/verilated_vcd_c.cpp:29: # In function ‘int open(const char*, int, ...)’, # inlined from ‘virtual bool VerilatedVcdFile::open(const string&)’ at # /usr/local/share/verilator/include/verilated_vcd_c.cpp:116:18: # /usr/include/x86_64-linux-gnu/bits/fcntl2.h:44:26: error: call to # ‘__open_too_many_args’ declared with attribute error: open can be called either # with 2 or 3 arguments, not more # __open_too_many_args (); # ~~~~~~~~~~~~~~~~~~~~~^~ # /usr/include/x86_64-linux-gnu/bits/fcntl2.h:50:24: error: call to # ‘__open_missing_mode’ declared with attribute error: open with O_CREAT or # O_TMPFILE in second argument needs 3 arguments # __open_missing_mode (); # ~~~~~~~~~~~~~~~~~~~~^~ # Shunning (7/7/2020): Use O0 here because O1 will lead to corruptions # in the CFFI-managed shared library pools. Basically what happened # is if we run two tests with the same component name but different # contents in a row, the second one uses the first library instead # of the second ... # (7/9/2020): Use -O0 by default so that normally the tests are super fast and don't corrupt cffi, # but when the user gives a "fast" flag, it uses -O1. if s.fast: c_flags = "-O1 -fno-guess-branch-probability -fno-reorder-blocks -fno-if-conversion -fno-if-conversion2 -fno-dce -fno-delayed-branch -fno-dse -fno-auto-inc-dec -fno-branch-count-reg -fno-combine-stack-adjustments -fno-cprop-registers -fno-forward-propagate -fno-inline-functions-called-once -fno-ipa-profile -fno-ipa-pure-const -fno-ipa-reference -fno-move-loop-invariants -fno-omit-frame-pointer -fno-split-wide-types -fno-tree-bit-ccp -fno-tree-ccp -fno-tree-ch -fno-tree-coalesce-vars -fno-tree-copy-prop -fno-tree-dce -fno-tree-dominator-opts -fno-tree-dse -fno-tree-fre -fno-tree-phiprop -fno-tree-pta -fno-tree-scev-cprop -fno-tree-sink -fno-tree-slsr -fno-tree-sra -fno-tree-ter -fno-tree-reassoc -fPIC -fno-gnu-unique -shared" else: c_flags = "-O0 -fno-guess-branch-probability -fno-reorder-blocks -fno-if-conversion -fno-if-conversion2 -fno-dce -fno-delayed-branch -fno-dse -fno-auto-inc-dec -fno-branch-count-reg -fno-combine-stack-adjustments -fno-cprop-registers -fno-forward-propagate -fno-inline-functions-called-once -fno-ipa-profile -fno-ipa-pure-const -fno-ipa-reference -fno-move-loop-invariants -fno-omit-frame-pointer -fno-split-wide-types -fno-tree-bit-ccp -fno-tree-ccp -fno-tree-ch -fno-tree-coalesce-vars -fno-tree-copy-prop -fno-tree-dce -fno-tree-dominator-opts -fno-tree-dse -fno-tree-fre -fno-tree-phiprop -fno-tree-pta -fno-tree-scev-cprop -fno-tree-sink -fno-tree-slsr -fno-tree-sra -fno-tree-ter -fno-tree-reassoc -fPIC -fno-gnu-unique -shared" if not s.is_default("c_flags"): c_flags += f" {expand(s.c_flags)}" c_include_path = " ".join("-I" + p for p in s._get_all_includes() if p) out_file = s.get_shared_lib_path() c_src_files = " ".join(s._get_c_src_files()) ld_flags = expand(s.ld_flags) ld_libs = s.ld_libs coverage = "-DVM_COVERAGE" if s.vl_coverage or \ s.vl_line_coverage or \ s.vl_toggle_coverage else "" return f"g++ {c_flags} {c_include_path} {ld_flags}"\ f" -o {out_file} {c_src_files} {ld_libs} {coverage}"
class VerilogPlaceholderConfigs(PlaceholderConfigs): VerilogOptions = { # Parameters # Map the names of parameters to their values # If {} is provided, use the parameters inferred from `construct` instead "params": {}, # Port name mapping # Map PyMTL port names to external port names "port_map": {}, # Expects the name of the top component in external source files # "" to use name of the current component to be imported "top_module": "", # Expects path of the file that contains the top module "src_file": "", # -f # Expects the path to the flist file; "" to disable this option "v_flist": "", # -v # Expects a list of paths to Verilog files; [] to disable this option "v_libs": [], # -I ( alias of -y and +incdir+ ) # Expects a list of include paths; [] to disable this option "v_include": [], # The separator used for name mangling "separator": '__', } VerilogCheckers = { ("params", "port_map"): Checker(lambda v: isinstance(v, dict), "expects a dict"), "top_module": Checker(lambda v: isinstance(v, str) and v, "expects a non-empty string"), "src_file": Checker( lambda v: isinstance(v, str) and (os.path.isfile(expand(v)) or not v), "src_file should be a path to a file or an empty string!"), "v_flist": Checker( lambda v: isinstance(v, str) and os.path.isfile(expand(v)) or v == "", "expects a path to a file"), "v_libs": Checker( lambda v: isinstance(v, list) and all( os.path.exists(expand(p)) for p in v), "expects a list of paths to files"), "v_include": Checker( lambda v: isinstance(v, list) and all( os.path.isdir(expand(p)) for p in v), "expects a list of paths to directory"), } Pass = VerilogPlaceholderPass def __new__(cls, m): inst = super().__new__(cls) # Do not pollute the attributes of the parent class cls.Options = deepcopy(PlaceholderConfigs.Options) cls.Checkers = deepcopy(PlaceholderConfigs.Checkers) for key, val in cls.VerilogOptions.items(): assert key not in cls.Options,\ f'config {key} is duplicated between PlaceholderConfigs and VerilogPlaceholderConfigs' cls.Options[key] = val for cfgs, chk in cls.VerilogCheckers.items(): if isinstance(cfgs, tuple): for cfg in cfgs: inst._add_to_checkers(cls.Checkers, cfg, chk) elif isinstance(cfgs, str): inst._add_to_checkers(cls.Checkers, cfgs, chk) return inst # Override def check(s): super().check() # Exactly one of src_file and v_flist should be non-empty if (s.v_flist and os.path.isfile(expand(s.src_file))) or \ (not s.v_flist and not os.path.isfile(expand(s.src_file))): raise InvalidPassOptionValue( 'src_file', s.src_file, s.Pass.__name__, 'exactly one of src_file and v_flist should be non-emtpy!') def get_port_map(s): pmap = {p._dsl._my_name: name for p, name in s.port_map.items()} return lambda name: pmap[name] if name in pmap else name def _add_to_checkers(s, checkers, cfg, chk): assert cfg not in checkers,\ f'config {cfg} is duplicated between PlaceholderConfigs!' checkers[cfg] = chk
class VerilogPlaceholderConfigs(PlaceholderConfigs): VerilogOptions = { # Parameters # Map the names of parameters to their values # If {} is provided, use the parameters inferred from `construct` instead "params": {}, # Port name mapping # Map PyMTL port names to external port names "port_map": {}, # Expects the name of the top component in external source files # "" to use name of the current component to be imported "top_module": "", # Expects path of the file that contains the top module "src_file": "", # -f # Expects the path to the flist file; "" to disable this option "v_flist": "", # -I ( alias of -y and +incdir+ ) # Expects a list of include paths; [] to disable this option "v_include": [], } VerilogCheckers = { ("params", "port_map"): Checker(lambda v: isinstance(v, dict), "expects a dict"), "top_module": Checker(lambda v: isinstance(v, str) and v, "expects a non-empty string"), "src_file": Checker( lambda v: isinstance(v, str) and (os.path.isfile(expand(v)) or not v), "src_file should be a path to a file or an empty string!"), "v_flist": Checker( lambda v: isinstance(v, str) and os.path.isfile(expand(v)) or v == "", "expects a path to a file"), "v_include": Checker( lambda v: isinstance(v, list) and all( os.path.isdir(expand(p)) for p in v), "expects a list of paths to directory"), } PassName = 'VerilogPlaceholderConfigs' def __new__(cls, *args, **kwargs): inst = super().__new__(cls) assert len(args) == 0, "We only accept keyword arguments here." cls.Options = deepcopy(PlaceholderConfigs.Options) cls.Checkers = deepcopy(PlaceholderConfigs.Checkers) for key, val in cls.VerilogOptions.items(): assert key not in cls.Options,\ f'config {key} is duplicated between PlaceholderConfigs and VerilogPlaceholderConfigs' cls.Options[key] = val all_checkers = inst._get_all_checker_configs(cls) for cfgs, chk in cls.VerilogCheckers.items(): if isinstance(cfgs, tuple): for cfg in cfgs: inst._add_to_checkers(cls.Checkers, cfg, chk) elif isinstance(cfgs, str): inst._add_to_checkers(cls.Checkers, cfgs, chk) return inst # Override def check(s): super().check() # Exactly one of src_file and v_flist should be non-empty if (s.v_flist and os.path.isfile(expand(s.src_file))) or \ (not s.v_flist and not os.path.isfile(expand(s.src_file))): raise InvalidPassOptionValue( 'src_file', s.src_file, s.PassName, 'exactly one of src_file and v_flist should be non-emtpy!') def get_port_map(s): pmap = s.port_map return lambda name: pmap[name] if name in pmap else name def _get_all_checker_configs(s, cls): ret = [] for cfgs in cls.Checkers.keys(): if isinstance(cfgs, tuple): for cfg in cfgs: ret.append(cfg) elif isinstance(cfgs, str): ret.append(cfgs) return ret def _add_to_checkers(s, checkers, cfg, chk): assert cfg not in checkers,\ f'config {cfg} is duplicated between PlaceholderConfigs!' checkers[cfg] = chk
class VerilatorImportConfigs(BasePassConfigs): Options = { # Enable verbose mode? "verbose": False, # Enable external line trace? # Once enabled, the `line_trace()` method of the imported component # will return a string read from the external `line_trace()` function. # This means your Verilog module has to have a `line_trace` function # that provides the line trace string which has less than 512 characters. # Default to False "vl_line_trace": False, # Enable all verilator coverage "vl_coverage": False, # Enable all verilator coverage "vl_line_coverage": False, # Enable all verilator coverage "vl_toggle_coverage": False, # Verilator code generation options # These options will be passed to verilator to generate the C simulator. # By default, verilator is called with `--cc`. # --Mdir # Expects the path of Makefile output directory; # "" to use `obj_dir_<translated_top_module>` "vl_mk_dir": "", # --assert # Expects a boolean value "vl_enable_assert": True, # Verilator optimization options # -O0/3 # Expects a non-negative integer # Currently only support 0 (disable opt) and 3 (highest effort opt) "vl_opt_level": 3, # --unroll-count # Expects a non-negative integer # 0 to disable this option "vl_unroll_count": 1000000, # --unroll-stmts # Expects a non-negative integer # 0 to disable this option "vl_unroll_stmts": 1000000, # Verilator warning-related options # False to disable the warnings, True to enable "vl_W_lint": True, "vl_W_style": True, "vl_W_fatal": True, # Un-warn all warnings in the given list; [] to disable this option # The given list should only include strings that appear in `Warnings` "vl_Wno_list": ['UNOPTFLAT', 'UNSIGNED', 'WIDTH'], # Verilator misc options # What is the inital value of signals? # Should be one of ['zeros', 'ones', 'rand'] "vl_xinit": "zeros", # --trace # Expects a boolean value "vl_trace": False, # The output filename of Verilator VCD tracing # default is {component_name}.verilator1 "vl_trace_filename": "", # Passed to verilator tracing function "vl_trace_timescale": "10ps", # `vl_trace_cycle_time`*`vl_trace_timescale` is the cycle time of the # PyMTL clock that appears in the generated VCD # With the default options, the frequency of PyMTL clock is 1GHz "vl_trace_cycle_time": 100, # C-compilation options # These options will be passed to the C compiler to create a shared lib. # Additional flags to be passed to the C compiler. # By default, CC is called with `-O0 -fPIC -shared`. # "" to disable this option "c_flags": "", # Additional include search path of the C compiler. # [] to disable this option "c_include_path": [], # Additional C source files passed to the C compiler. # [] to compile verilator generated files only. "c_srcs": [], # `LDLIBS` will be listed after the primary target file whereas # `LDFLAGS` will be listed before. # We enforce the GNU makefile implicit rule that `LDFLAGS` should only # include non-library linker flags such as `-L`. "ld_flags": "", # We enforce the GNU makefile implicit rule that `LDLIBS` should only # include library linker flags/names such as `-lfoo`. "ld_libs": "", } Checkers = { ("verbose", "vl_enable_assert", "vl_line_trace", "vl_W_lint", "vl_W_style", "vl_W_fatal", "vl_trace", "vl_coverage", "vl_line_coverage", "vl_toggle_coverage"): Checker( lambda v: isinstance(v, bool), "expects a boolean" ), ("c_flags", "ld_flags", "ld_libs", "vl_trace_filename"): Checker( lambda v: isinstance(v, str), "expects a string" ), ("vl_opt_level", "vl_unroll_count", "vl_unroll_stmts"): Checker( lambda v: isinstance(v, int) and v >= 0, "expects an integer >= 0" ), "vl_Wno_list": Checker( lambda v: isinstance(v, list) and all(w in VerilogPlaceholderConfigs.Warnings for w in v), "expects a list of warnings" ), "vl_xinit": Checker( lambda v: (v in ['zeros', 'ones', 'rand']) or isinstance(v, int), "vl_xinit should be an integer or one of ['zeros', 'ones', 'rand']" ), "vl_trace_timescale": Checker( lambda v: isinstance(v, str) and len(v) > 2 and v[-1] == 's' and \ v[-2] in ['p', 'n', 'u', 'm'] and \ all(c.isdigit() for c in v[:-2]), "expects a timescale string" ), "vl_trace_cycle_time": Checker( lambda v: isinstance(v, int) and (v % 2) == 0, "expects an integer `n` such that `n`*`vl_trace_timescale` is the cycle time" ), "vl_mk_dir": Checker( lambda v: isinstance(v, str), "expects a path to directory" ), "c_include_path": Checker( lambda v: isinstance(v, list) and all(os.path.isdir(expand(p)) for p in v), "expects a list of paths to directories" ), "c_srcs": Checker( lambda v: isinstance(v, list) and all(os.path.isfile(expand(p)) for p in v), "expects a list of paths to files" ) } Warnings = [ 'ALWCOMBORDER', 'ASSIGNIN', 'ASSIGNDLY', 'BLKANDNBLK', 'BLKSEQ', 'BLKLOOPINIT', 'BSSPACE', 'CASEINCOMPLETE', 'CASEOVERLAP', 'CASEX', 'CASEWITHX', 'CDCRSTLOGIC', 'CLKDATA', 'CMPCONST', 'COLONPLUS', 'COMBDLY', 'CONTASSREG', 'DECLFILENAME', 'DEFPARAM', 'DETECTARRAY', 'ENDLABEL', 'GENCLK', 'IFDEPTH', 'IGNOREDRETURN', 'IMPERFECTSCH', 'IMPLICIT', 'IMPORTSTAR', 'IMPURE', 'INCABSPATH', 'INFINITELOOP', 'INITIALDLY', 'LITENDIAN', 'MODDUP', 'MULTIDRIVEN', 'MULTITOP', 'PINCONNECTEMPTY', 'PINMISSING', 'PINNOCONNECT', 'PROCASSWIRE', 'REALCVT', 'REDEFMACRO', 'SELRANGE', 'STMTDLY', 'SYMRSVDWORD', 'SYNCASYNCNET', 'TASKNSVAR', 'TICKCOUNT', 'UNDRIVEN', 'UNOPT', 'UNOPTFLAT', 'UNOPTTHREADS', 'UNPACKED', 'UNSIGNED', 'UNUSED', 'USERINFO', 'USERWARN', 'USERERROR', 'USERFATAL', 'VARHIDDEN', 'WIDTH', 'WIDTHCONCAT', ] PassName = 'VerilatorImportConfigs' #--------------------------------------------------- # Public APIs #--------------------------------------------------- def setup_configs(s, m, m_tr_namespace): # VerilatorImportConfigs alone does not have the complete information about # the module to be imported. For example, we need to read from the placeholder # configuration to figure out the pickled file name and the top module name. # This method is meant to be called before calling other public APIs. s.translated_top_module = m_tr_namespace.translated_top_module s.translated_source_file = m_tr_namespace.translated_filename s.v_include = m.config_placeholder.v_include s.v_libs = m.config_placeholder.v_libs # s.src_file = m.config_placeholder.src_file s.port_map = m.config_placeholder.port_map s.params = m.config_placeholder.params if not s.vl_mk_dir: s.vl_mk_dir = f'obj_dir_{s.translated_top_module}' def get_vl_xinit_value(s): if s.vl_xinit == 'zeros': return 0 elif s.vl_xinit == 'ones': return 1 elif (s.vl_xinit == 'rand') or isinstance(s.vl_xinit, int): return 2 else: raise InvalidPassOptionValue( "vl_xinit should be an int or one of 'zeros', 'ones', or 'rand'!" ) def get_vl_xinit_seed(s): if isinstance(s.vl_xinit, int): return s.vl_xinit else: return 0 def get_c_wrapper_path(s): return f'{s.translated_top_module}_v.cpp' def get_py_wrapper_path(s): return f'{s.translated_top_module}_v.py' def get_shared_lib_path(s): return f'lib{s.translated_top_module}_v.so' #--------------------- # Command generation #--------------------- def create_vl_cmd(s): top_module = f"--top-module {s.translated_top_module}" src = s.translated_source_file mk_dir = f"--Mdir {s.vl_mk_dir}" # flist = "" if s.is_default("v_flist") else \ # f"-f {s.v_flist}" # We don't need this since all v_libs appear in the translation result # vlibs = "" if not s.v_libs else \ # " ".join([f"-v {lib}" for lib in s.v_libs]) vlibs = "" include = "" if not s.v_include else \ " ".join("-I" + path for path in s.v_include) en_assert = "--assert" if s.vl_enable_assert else "" opt_level = "-O3" if s.is_default('vl_opt_level') else "-O0" loop_unroll = "" if s.vl_unroll_count == 0 else \ f"--unroll-count {s.vl_unroll_count}" stmt_unroll = "" if s.vl_unroll_stmts == 0 else \ f"--unroll-stmts {s.vl_unroll_stmts}" trace = "--trace" if s.vl_trace else "" coverage = "--coverage" if s.vl_coverage else "" line_cov = "--coverage-line" if s.vl_line_coverage else "" toggle_cov = "--coverage-toggle" if s.vl_toggle_coverage else "" warnings = s._create_vl_warning_cmd() all_opts = [ top_module, mk_dir, include, en_assert, opt_level, loop_unroll, # stmt_unroll, trace, warnings, flist, src, coverage, stmt_unroll, trace, warnings, src, vlibs, coverage, line_cov, toggle_cov, ] return f"verilator --cc {' '.join(opt for opt in all_opts if opt)}" def create_cc_cmd(s): c_flags = "-O0 -fPIC -fno-gnu-unique -shared" + \ ("" if s.is_default("c_flags") else f" {expand(s.c_flags)}") c_include_path = " ".join("-I" + p for p in s._get_all_includes() if p) out_file = s.get_shared_lib_path() c_src_files = " ".join(s._get_c_src_files()) ld_flags = expand(s.ld_flags) ld_libs = s.ld_libs coverage = "-DVM_COVERAGE" if s.vl_coverage or \ s.vl_line_coverage or \ s.vl_toggle_coverage else "" return f"g++ {c_flags} {c_include_path} {ld_flags}"\ f" -o {out_file} {c_src_files} {ld_libs} {coverage}" def vprint(s, msg, nspaces=0, use_fill=False): if s.verbose: if use_fill: print(indent(fill(msg), " " * nspaces)) else: print(indent(msg, " " * nspaces)) #--------------------- # Internal helpers #--------------------- def _create_vl_warning_cmd(s): lint = "" if s.is_default("vl_W_lint") else "--Wno-lint" style = "" if s.is_default("vl_W_style") else "--Wno-style" fatal = "" if s.is_default("vl_W_fatal") else "--Wno-fatal" wno = " ".join(f"--Wno-{w}" for w in s.vl_Wno_list) return " ".join(w for w in [lint, style, fatal, wno] if w) def _get_all_includes(s): includes = s.c_include_path # Try to obtain verilator include path either from environment variable # or from `pkg-config` vl_include_dir = os.environ.get("PYMTL_VERILATOR_INCLUDE_DIR") if vl_include_dir is None: get_dir_cmd = ["pkg-config", "--variable=includedir", "verilator"] try: vl_include_dir = \ subprocess.check_output(get_dir_cmd, stderr = subprocess.STDOUT).strip() vl_include_dir = vl_include_dir.decode('ascii') except OSError as e: vl_include_dir_msg = \ """\ Cannot locate the include directory of verilator. Please make sure either \ $PYMTL_VERILATOR_INCLUDE_DIR is set or `pkg-config` has been configured properly! """ raise OSError(fill(vl_include_dir_msg)) from e # Add verilator include path s.vl_include_dir = vl_include_dir includes += [vl_include_dir, vl_include_dir + "/vltstd"] return includes def _get_c_src_files(s): srcs = s.c_srcs top_module = s.translated_top_module vl_mk_dir = s.vl_mk_dir vl_class_mk = f"{vl_mk_dir}/V{top_module}_classes.mk" # Add C wrapper srcs.append(s.get_c_wrapper_path()) # Add files listed in class makefile with open(vl_class_mk) as class_mk: srcs += s._get_srcs_from_vl_class_mk(class_mk, vl_mk_dir, "VM_CLASSES_FAST") srcs += s._get_srcs_from_vl_class_mk(class_mk, vl_mk_dir, "VM_CLASSES_SLOW") srcs += s._get_srcs_from_vl_class_mk(class_mk, vl_mk_dir, "VM_SUPPORT_FAST") srcs += s._get_srcs_from_vl_class_mk(class_mk, vl_mk_dir, "VM_SUPPORT_SLOW") srcs += s._get_srcs_from_vl_class_mk(class_mk, s.vl_include_dir, "VM_GLOBAL_FAST") srcs += s._get_srcs_from_vl_class_mk(class_mk, s.vl_include_dir, "VM_GLOBAL_SLOW") return srcs def _get_srcs_from_vl_class_mk(s, mk, path, label): """Return all files under `path` directory in `label` section of `mk`.""" srcs, found = [], False mk.seek(0) for line in mk: if line.startswith(label): found = True elif found: if line.strip() == "": found = False else: file_name = line.strip()[:-2] srcs.append(path + "/" + file_name + ".cpp") return srcs
class VerilogVerilatorImportConfigs(BasePassConfigs): Options = { # Import this component? "enable": False, # Enable verbose mode? "verbose": False, # Enable external line trace? # Once enabled, the `line_trace()` method of the imported component # will return a string read from the external `line_trace()` function. # This means your Verilog module has to have a `line_trace` function # that provides the line trace string which has less than 512 characters. # Default to False "vl_line_trace": False, # Enable all verilator coverage "vl_coverage": False, # Enable all verilator coverage "vl_line_coverage": False, # Enable all verilator coverage "vl_toggle_coverage": False, # Verilator code generation options # These options will be passed to verilator to generate the C simulator. # By default, verilator is called with `--cc`. # --Mdir # Expects the path of Makefile output directory; # "" to use `obj_dir_<translated_top_module>` "vl_mk_dir": "", # --assert # Expects a boolean value "vl_enable_assert": True, # Verilator optimization options # -O0/3 # Expects a non-negative integer # Currently only support 0 (disable opt) and 3 (highest effort opt) "vl_opt_level": 3, # --unroll-count # Expects a non-negative integer # 0 to disable this option "vl_unroll_count": 1000000, # --unroll-stmts # Expects a non-negative integer # 0 to disable this option "vl_unroll_stmts": 1000000, # Verilator warning-related options # False to disable the warnings, True to enable "vl_W_lint": True, "vl_W_style": True, "vl_W_fatal": True, # Un-warn all warnings in the given list; [] to disable this option # The given list should only include strings that appear in `Warnings` "vl_Wno_list": ['UNOPTFLAT', 'UNSIGNED', 'WIDTH'], # Verilator misc options # What is the inital value of signals? # Should be one of ['zeros', 'ones', 'rand'] "vl_xinit": "zeros", # --trace # Expects a boolean value "vl_trace": False, # The output filename of Verilator VCD tracing # default is {component_name}.verilator1 "vl_trace_filename": "", # Passed to verilator tracing function "vl_trace_timescale": "10ps", # `vl_trace_cycle_time`*`vl_trace_timescale` is the cycle time of the # PyMTL clock that appears in the generated VCD # With the default options, the frequency of PyMTL clock is 1GHz "vl_trace_cycle_time": 100, # C-compilation options # These options will be passed to the C compiler to create a shared lib. # Additional flags to be passed to the C compiler. # By default, CC is called with `-O0 -fPIC -shared`. # "" to disable this option "c_flags": "", # Additional include search path of the C compiler. # [] to disable this option "c_include_path": [], # Additional C source files passed to the C compiler. # [] to compile verilator generated files only. "c_srcs": [], # `LDLIBS` will be listed after the primary target file whereas # `LDFLAGS` will be listed before. # We enforce the GNU makefile implicit rule that `LDFLAGS` should only # include non-library linker flags such as `-L`. "ld_flags": "", # We enforce the GNU makefile implicit rule that `LDLIBS` should only # include library linker flags/names such as `-lfoo`. "ld_libs": "", } Checkers = { ("enable", "verbose", "vl_enable_assert", "vl_line_trace", "vl_W_lint", "vl_W_style", "vl_W_fatal", "vl_trace", "vl_coverage", "vl_line_coverage", "vl_toggle_coverage"): Checker( lambda v: isinstance(v, bool), "expects a boolean" ), ("c_flags", "ld_flags", "ld_libs", "vl_trace_filename"): Checker( lambda v: isinstance(v, str), "expects a string" ), ("vl_opt_level", "vl_unroll_count", "vl_unroll_stmts"): Checker( lambda v: isinstance(v, int) and v >= 0, "expects an integer >= 0" ), "vl_Wno_list": Checker( lambda v: isinstance(v, list) and all(w in VerilogPlaceholderConfigs.Warnings for w in v), "expects a list of warnings" ), "vl_xinit": Checker( lambda v: (v in ['zeros', 'ones', 'rand']) or isinstance(v, int), "vl_xinit should be an integer or one of ['zeros', 'ones', 'rand']" ), "vl_trace_timescale": Checker( lambda v: isinstance(v, str) and len(v) > 2 and v[-1] == 's' and \ v[-2] in ['p', 'n', 'u', 'm'] and \ all(c.isdigit() for c in v[:-2]), "expects a timescale string" ), "vl_trace_cycle_time": Checker( lambda v: isinstance(v, int) and (v % 2) == 0, "expects an integer `n` such that `n`*`vl_trace_timescale` is the cycle time" ), "vl_mk_dir": Checker( lambda v: isinstance(v, str), "expects a path to directory" ), "c_include_path": Checker( lambda v: isinstance(v, list) and all(os.path.isdir(expand(p)) for p in v), "expects a list of paths to directories" ), "c_srcs": Checker( lambda v: isinstance(v, list) and all(os.path.isfile(expand(p)) for p in v), "expects a list of paths to files" ) } Warnings = [ 'ALWCOMBORDER', 'ASSIGNIN', 'ASSIGNDLY', 'BLKANDNBLK', 'BLKSEQ', 'BLKLOOPINIT', 'BSSPACE', 'CASEINCOMPLETE', 'CASEOVERLAP', 'CASEX', 'CASEWITHX', 'CDCRSTLOGIC', 'CLKDATA', 'CMPCONST', 'COLONPLUS', 'COMBDLY', 'CONTASSREG', 'DECLFILENAME', 'DEFPARAM', 'DETECTARRAY', 'ENDLABEL', 'GENCLK', 'IFDEPTH', 'IGNOREDRETURN', 'IMPERFECTSCH', 'IMPLICIT', 'IMPORTSTAR', 'IMPURE', 'INCABSPATH', 'INFINITELOOP', 'INITIALDLY', 'LITENDIAN', 'MODDUP', 'MULTIDRIVEN', 'MULTITOP', 'PINCONNECTEMPTY', 'PINMISSING', 'PINNOCONNECT', 'PROCASSWIRE', 'REALCVT', 'REDEFMACRO', 'SELRANGE', 'STMTDLY', 'SYMRSVDWORD', 'SYNCASYNCNET', 'TASKNSVAR', 'TICKCOUNT', 'UNDRIVEN', 'UNOPT', 'UNOPTFLAT', 'UNOPTTHREADS', 'UNPACKED', 'UNSIGNED', 'UNUSED', 'USERINFO', 'USERWARN', 'USERERROR', 'USERFATAL', 'VARHIDDEN', 'WIDTH', 'WIDTHCONCAT', ] Pass = VerilogVerilatorImportPass #--------------------------------------------------- # Public APIs #--------------------------------------------------- def setup_configs(s, m, tr_pass, ph_pass): # VerilatorImportConfigs alone does not have the complete information about # the module to be imported. For example, we need to read from the placeholder # configuration to figure out the pickled file name and the top module name. # This method is meant to be called before calling other public APIs. s.translated_top_module = m.get_metadata(tr_pass.translated_top_module) s.translated_source_file = m.get_metadata(tr_pass.translated_filename) ph_cfg = m.get_metadata(ph_pass.placeholder_config) s.v_include = ph_cfg.v_include s.v_libs = ph_cfg.v_libs s.src_file = ph_cfg.src_file s.port_map = ph_cfg.port_map s.params = ph_cfg.params # Infer vl_line_trace by scanning through the source file try: trim = lambda s: ''.join(s.split()) if not s.vl_line_trace: with open(s.src_file) as fd: for line in fd.readlines(): if "`VC_TRACE_BEGIN" in trim(line): s.vl_line_trace = True break except OSError: pass if not s.vl_mk_dir: s.vl_mk_dir = f'obj_dir_{s.translated_top_module}' def get_vl_xinit_value(s): if s.vl_xinit == 'zeros': return 0 elif s.vl_xinit == 'ones': return 1 elif (s.vl_xinit == 'rand') or isinstance(s.vl_xinit, int): return 2 else: raise InvalidPassOptionValue( "vl_xinit should be an int or one of 'zeros', 'ones', or 'rand'!" ) def get_vl_xinit_seed(s): if isinstance(s.vl_xinit, int): return s.vl_xinit else: return 0 def get_c_wrapper_path(s): return f'{s.translated_top_module}_v.cpp' def get_py_wrapper_path(s): return f'{s.translated_top_module}_v.py' def get_shared_lib_path(s): return f'lib{s.translated_top_module}_v.so' #--------------------- # Command generation #--------------------- def create_vl_cmd(s): top_module = f"--top-module {s.translated_top_module}" src = s.translated_source_file mk_dir = f"--Mdir {s.vl_mk_dir}" # flist = "" if s.is_default("v_flist") else \ # f"-f {s.v_flist}" # We don't need this since all v_libs appear in the translation result # vlibs = "" if not s.v_libs else \ # " ".join([f"-v {lib}" for lib in s.v_libs]) vlibs = "" include = "" if not s.v_include else \ " ".join("-I" + path for path in s.v_include) en_assert = "--assert" if s.vl_enable_assert else "" opt_level = "-O3" if s.is_default('vl_opt_level') else "-O0" loop_unroll = "" if s.vl_unroll_count == 0 else \ f"--unroll-count {s.vl_unroll_count}" stmt_unroll = "" if s.vl_unroll_stmts == 0 else \ f"--unroll-stmts {s.vl_unroll_stmts}" trace = "--trace" if s.vl_trace else "" coverage = "--coverage" if s.vl_coverage else "" line_cov = "--coverage-line" if s.vl_line_coverage else "" toggle_cov = "--coverage-toggle" if s.vl_toggle_coverage else "" warnings = s._create_vl_warning_cmd() all_opts = [ top_module, mk_dir, include, en_assert, opt_level, loop_unroll, # stmt_unroll, trace, warnings, flist, src, coverage, stmt_unroll, trace, warnings, src, vlibs, coverage, line_cov, toggle_cov, ] return f"verilator --cc {' '.join(opt for opt in all_opts if opt)}" def create_cc_cmd(s): # Shunning: GCC has a family of optimizations on the SSA tree -ftree-***. # It belongs to -O1, and takes a lot of time to execute when I compile the verilated C++. # However, the compiled code has worse performance after applying those tree optimizations. # I guess it's because verilator already emits very regular and low-level code in the form of C++. # Also gcc -O1, -O2, -O3 result in the same compilation time after all optimization flags are turned off # gcc -O0 still has shorter compilation time than O1-3 with all flags off # My guess is that -O1 has some hidden optimization that the user cannot turn off, and -O2-3 inherit that part # the difference between O1-3 is just flags # so right now I'm using "-O1 with all options off" to get the fastest compilation time and good performance # !!! -fno-tree-forwprop is removed due to: # In file included from /usr/include/fcntl.h:290:0, # from /usr/local/share/verilator/include/verilated_vcd_c.cpp:29: # In function ‘int open(const char*, int, ...)’, # inlined from ‘virtual bool VerilatedVcdFile::open(const string&)’ at # /usr/local/share/verilator/include/verilated_vcd_c.cpp:116:18: # /usr/include/x86_64-linux-gnu/bits/fcntl2.h:44:26: error: call to # ‘__open_too_many_args’ declared with attribute error: open can be called either # with 2 or 3 arguments, not more # __open_too_many_args (); # ~~~~~~~~~~~~~~~~~~~~~^~ # /usr/include/x86_64-linux-gnu/bits/fcntl2.h:50:24: error: call to # ‘__open_missing_mode’ declared with attribute error: open with O_CREAT or # O_TMPFILE in second argument needs 3 arguments # __open_missing_mode (); # ~~~~~~~~~~~~~~~~~~~~^~ c_flags = "-O1 -fno-guess-branch-probability -fno-reorder-blocks -fno-if-conversion -fno-if-conversion2 -fno-dce -fno-delayed-branch -fno-dse -fno-auto-inc-dec -fno-branch-count-reg -fno-combine-stack-adjustments -fno-cprop-registers -fno-forward-propagate -fno-inline-functions-called-once -fno-ipa-profile -fno-ipa-pure-const -fno-ipa-reference -fno-move-loop-invariants -fno-omit-frame-pointer -fno-split-wide-types -fno-tree-bit-ccp -fno-tree-ccp -fno-tree-ch -fno-tree-coalesce-vars -fno-tree-copy-prop -fno-tree-dce -fno-tree-dominator-opts -fno-tree-dse -fno-tree-fre -fno-tree-phiprop -fno-tree-pta -fno-tree-scev-cprop -fno-tree-sink -fno-tree-slsr -fno-tree-sra -fno-tree-ter -fno-tree-reassoc -fPIC -fno-gnu-unique -shared" + \ ("" if s.is_default("c_flags") else f" {expand(s.c_flags)}") c_include_path = " ".join("-I" + p for p in s._get_all_includes() if p) out_file = s.get_shared_lib_path() c_src_files = " ".join(s._get_c_src_files()) ld_flags = expand(s.ld_flags) ld_libs = s.ld_libs coverage = "-DVM_COVERAGE" if s.vl_coverage or \ s.vl_line_coverage or \ s.vl_toggle_coverage else "" return f"g++ {c_flags} {c_include_path} {ld_flags}"\ f" -o {out_file} {c_src_files} {ld_libs} {coverage}" def vprint(s, msg, nspaces=0, use_fill=False): if s.verbose: if use_fill: print(indent(fill(msg), " " * nspaces)) else: print(indent(msg, " " * nspaces)) #--------------------- # Internal helpers #--------------------- def _create_vl_warning_cmd(s): lint = "" if s.is_default("vl_W_lint") else "--Wno-lint" style = "" if s.is_default("vl_W_style") else "--Wno-style" fatal = "" if s.is_default("vl_W_fatal") else "--Wno-fatal" wno = " ".join(f"--Wno-{w}" for w in s.vl_Wno_list) return " ".join(w for w in [lint, style, fatal, wno] if w) def _get_all_includes(s): includes = copy.copy(s.c_include_path) # Try to obtain verilator include path either from environment variable # or from `pkg-config` vl_include_dir = os.environ.get("PYMTL_VERILATOR_INCLUDE_DIR") if vl_include_dir is None: get_dir_cmd = ["pkg-config", "--variable=includedir", "verilator"] try: vl_include_dir = \ subprocess.check_output(get_dir_cmd, stderr = subprocess.STDOUT).strip() vl_include_dir = vl_include_dir.decode('ascii') except OSError as e: vl_include_dir_msg = \ """\ Cannot locate the include directory of verilator. Please make sure either \ $PYMTL_VERILATOR_INCLUDE_DIR is set or `pkg-config` has been configured properly! """ raise OSError(fill(vl_include_dir_msg)) from e # Add verilator include path s.vl_include_dir = vl_include_dir includes += [vl_include_dir, vl_include_dir + "/vltstd"] return includes def _get_c_src_files(s): top_module = s.translated_top_module vl_mk_dir = s.vl_mk_dir vl_class_mk = f"{vl_mk_dir}/V{top_module}_classes.mk" # Add C wrapper fast = copy.copy(s.c_srcs) fast.append(s.get_c_wrapper_path()) slow = [] # Add files listed in class makefile with open(vl_class_mk) as class_mk: all_lines = class_mk.readlines() fast += s._get_srcs_from_vl_class_mk(all_lines, vl_mk_dir, "VM_CLASSES_FAST") fast += s._get_srcs_from_vl_class_mk(all_lines, vl_mk_dir, "VM_SUPPORT_FAST") fast += s._get_srcs_from_vl_class_mk(all_lines, s.vl_include_dir, "VM_GLOBAL_FAST") slow += s._get_srcs_from_vl_class_mk(all_lines, vl_mk_dir, "VM_CLASSES_SLOW") slow += s._get_srcs_from_vl_class_mk(all_lines, vl_mk_dir, "VM_SUPPORT_SLOW") slow += s._get_srcs_from_vl_class_mk(all_lines, s.vl_include_dir, "VM_GLOBAL_SLOW") with open(f"{top_module}_v__ALL_pickled.cpp", 'w') as out: out.write('\n'.join([f'#include "{x}"' for x in fast])) # Apply no optimization for SLOW files if slow: out.write('\n#pragma GCC push_options') out.write('\n#pragma GCC optimize ("O0")\n') out.write('\n'.join([f'#include "{x}"' for x in slow])) out.write('\n#pragma GCC pop_options\n') return [f"{top_module}_v__ALL_pickled.cpp"] def _get_srcs_from_vl_class_mk(s, all_lines, path, label): """Return all files under `path` directory in `label` section of `mk`.""" srcs, found = [], False for line in all_lines: if line.startswith(label): found = True elif found: if line.strip() == "": found = False else: file_name = line.strip()[:-2] srcs.append(path + "/" + file_name + ".cpp") return srcs