class BehavioralRTLIRTypeCheckL1Pass(RTLIRPass):

    # Pass metadata

    #: A dictionary that maps free variable names to (object, RTLIRType)
    #:
    #: Type: ``dict``; output
    rtlir_freevars = MetadataKey()

    #: A set of variable names that are accessed in the upblks
    #:
    #: Type: ``set(str)``; output
    rtlir_accessed = MetadataKey()

    def __init__(s, translation_top):
        c = s.__class__
        s.tr_top = translation_top
        if not translation_top.has_metadata(c.rtlir_getter):
            translation_top.set_metadata(c.rtlir_getter,
                                         rt.RTLIRGetter(cache=True))

    def __call__(s, m):
        """Perform type checking on all RTLIR in rtlir_upblks."""
        c = s.__class__

        rtlir_freevars = {}
        rtlir_accessed = set()

        m.set_metadata(BehavioralRTLIRTypeCheckL1Pass.rtlir_freevars,
                       rtlir_freevars)
        m.set_metadata(BehavioralRTLIRTypeCheckL1Pass.rtlir_accessed,
                       rtlir_accessed)

        type_checker = s.get_visitor_class()(
            m,
            rtlir_freevars,
            rtlir_accessed,
            s.tr_top.get_metadata(c.rtlir_getter),
        )

        rtlir_upblks = m.get_metadata(BehavioralRTLIRGenL1Pass.rtlir_upblks)

        for blk in m.get_update_block_order():
            type_checker.enter(blk, rtlir_upblks[blk])

    #-------------------------------------------------------------------------
    # Type checker
    #-------------------------------------------------------------------------

    def get_visitor_class(s):
        return BehavioralRTLIRTypeCheckVisitorL1
Esempio n. 2
0
class PlaceholderPass(BasePass):

    # Placeholder pass input pass data

    #: Enable pickling on the placeholder component.
    #:
    #: Type: ``bool``; input
    #:
    #: Default value: ``False``
    enable = MetadataKey(bool)

    #: Does the module of external source have clk pin?
    #:
    #: Type: ``bool``; input
    #:
    #: Default value: ``False``
    has_clk = MetadataKey(bool)

    #: Does the module of external source have reset pin?
    #:
    #: Type: ``bool``; input
    #:
    #: Default value: ``False``
    has_reset = MetadataKey(bool)

    # Placeholder pass output pass data

    #: An instance of :class:`PlaceholderConfigs` that contains the parsed options.
    #:
    #: Type: ``PlaceholderConfigs``; output
    placeholder_config = MetadataKey()

    def __call__(s, m):
        """Pickle every ``Placeholder`` in the component hierarchy rooted at ``m``."""
        if isinstance(m, Placeholder):
            s.visit_placeholder(m)
        for child in m.get_child_components(repr):
            s.__call__(child)

    def visit_placeholder(s, m):
        s.setup_configs(m)

    def setup_configs(s, m):
        c = s.__class__
        ph_config = c.get_placeholder_config()(m)
        m.set_metadata(c.placeholder_config, ph_config)

    @staticmethod
    def get_placeholder_config():
        from pymtl3.passes.PlaceholderConfigs import PlaceholderConfigs
        return PlaceholderConfigs
Esempio n. 3
0
class StructuralRTLIRGenL0Pass(RTLIRPass):

    # Pass metadata

    #: RTLIR type of the component
    #:
    #: Type: ``RTLIRType.Component``; output
    rtlir_type = MetadataKey()

    #: RTLIR of all constants that belong to the component
    #:
    #: Type: ``list``; output
    consts = MetadataKey()

    #: RTLIR of all connections in component
    #:
    #: Type: ``list``; output
    connections = MetadataKey()
Esempio n. 4
0
class BehavioralRTLIRGenL1Pass(RTLIRPass):

    # Pass metadata

    #: A dictionary that maps upblk functions to their BIR representation
    #:
    #: Type: ``dict``; output
    rtlir_upblks = MetadataKey()

    def __init__(s, translation_top):
        c = s.__class__
        s.tr_top = translation_top
        if not translation_top.has_metadata(c.rtlir_getter):
            translation_top.set_metadata(c.rtlir_getter,
                                         RTLIRGetter(cache=True))

    def __call__(s, m):
        """Generate RTLIR for all upblks of m."""
        c = s.__class__

        if m.has_metadata(c.rtlir_upblks):
            rtlir_upblks = m.get_metadata(c.rtlir_upblks)
        else:
            rtlir_upblks = {}
            m.set_metadata(c.rtlir_upblks, rtlir_upblks)

        visitor = s.get_rtlir_generator_class()(m)
        upblks = {
            bir.CombUpblk: get_ordered_upblks(m),
            bir.SeqUpblk: get_ordered_update_ff(m),
        }
        # Sort the upblks by their name
        upblks[bir.CombUpblk].sort(key=lambda x: x.__name__)
        upblks[bir.SeqUpblk].sort(key=lambda x: x.__name__)

        for upblk_type in (bir.CombUpblk, bir.SeqUpblk):
            for blk in upblks[upblk_type]:
                visitor._upblk_type = upblk_type
                upblk_info = m.get_update_block_info(blk)
                upblk = visitor.enter(blk, upblk_info[-1])
                upblk.is_lambda = upblk_info[0]
                upblk.src = upblk_info[1]
                upblk.lino = upblk_info[2]
                upblk.filename = upblk_info[3]
                rtlir_upblks[blk] = upblk

    def get_rtlir_generator_class(s):
        return BehavioralRTLIRGeneratorL1
class BehavioralRTLIRTypeCheckL2Pass(BehavioralRTLIRTypeCheckL1Pass):

    # Pass metadata

    #: A dictionary that maps tmp variable names to its RTLIRType
    #:
    #: Type: ``dict``; output
    rtlir_tmpvars = MetadataKey()

    def get_visitor_class(s):
        return BehavioralRTLIRTypeCheckVisitorL2

    def __call__(s, m):
        """Perform type checking on all RTLIR in rtlir_upblks."""
        c = s.__class__

        rtlir_freevars = {}
        rtlir_accessed = set()
        rtlir_tmpvars = {}

        m.set_metadata(BehavioralRTLIRTypeCheckL2Pass.rtlir_freevars,
                       rtlir_freevars)
        m.set_metadata(BehavioralRTLIRTypeCheckL2Pass.rtlir_accessed,
                       rtlir_accessed)
        m.set_metadata(BehavioralRTLIRTypeCheckL2Pass.rtlir_tmpvars,
                       rtlir_tmpvars)

        type_checker = s.get_visitor_class()(
            m,
            rtlir_freevars,
            rtlir_accessed,
            rtlir_tmpvars,
            s.tr_top.get_metadata(c.rtlir_getter),
        )

        rtlir_upblks = m.get_metadata(BehavioralRTLIRGenL1Pass.rtlir_upblks)

        for blk in m.get_update_block_order():
            type_checker.enter(blk, rtlir_upblks[blk])
class VerilogVerilatorImportPass(BasePass):
    """Import an arbitrary SystemVerilog module as a PyMTL component."""

    # Import pass input pass data

    #: Enable import on a component.
    #:
    #: Type: ``bool``; input
    #:
    #: Default value: ``False``
    enable = MetadataKey(bool)

    #: Print out extra debug information during import.
    #:
    #: Type: ``bool``; input
    #:
    #: Default value: ``False``
    verbose = MetadataKey(bool)

    #: Use Verilog ``line_trace`` output as the Python line_trace return value.
    #:
    #: Type: ``bool``; input
    #:
    #: Default value: ``False``
    vl_line_trace = MetadataKey(bool)

    #: Enable Verilator coverage.
    #:
    #: Type: ``bool``; input
    #:
    #: Default value: ``False``
    vl_coverage = MetadataKey(bool)

    #: Enable Verilator line coverage.
    #:
    #: Type: ``bool``; input
    #:
    #: Default value: ``False``
    vl_line_coverage = MetadataKey(bool)

    #: Enable Verilator toggle coverage.
    #:
    #: Type: ``bool``; input
    #:
    #: Default value: ``False``
    vl_toggle_coverage = MetadataKey(bool)

    #: Specify the Verilator make directory.
    #:
    #: Type: ``str``; input
    #:
    #: Default value: obj_<top-component-name>
    vl_mk_dir = MetadataKey(str)

    #: Enable Verilog assert.
    #:
    #: Type: ``bool``; input
    #:
    #: Default value: ``False``
    vl_enable_assert = MetadataKey(bool)

    #: Verilator optimization level.
    #:
    #: Type: ``int``; input
    #:
    #: Default value: ``3``
    vl_opt_level = MetadataKey(int)

    #: Verilator unroll count.
    #:
    #: Type: ``int``; input
    #:
    #: Default value: ``1000000``
    vl_unroll_count = MetadataKey(int)

    #: Verilator unroll statement count.
    #:
    #: Type: ``int``; input
    #:
    #: Default value: ``1000000``
    vl_unroll_stmts = MetadataKey(int)

    #: Enable Verilator lint warnings.
    #:
    #: Type: ``bool``; input
    #:
    #: Default value: ``True``
    vl_W_lint = MetadataKey(bool)

    #: Enable Verilator style warnings.
    #:
    #: Type: ``bool``; input
    #:
    #: Default value: ``True``
    vl_W_style = MetadataKey(bool)

    #: Enable Verilator fatal warnings.
    #:
    #: Type: ``bool``; input
    #:
    #: Default value: ``True``
    vl_W_fatal = MetadataKey(bool)

    #: A list of suppressed Verilator warnings.
    #:
    #: Type: ``[str]``; input
    #:
    #: Default value: ``['UNSIGNED', 'UNOPTFLAT', 'WIDTH']``
    vl_Wno_list = MetadataKey(list)

    #: Verilator initialization options.
    #:
    #: Possible values: ``'ones'``, ``'zeros'``, ``'rand'``, and non-zero integers; input
    #:
    #: Default value: ``'zeros'``
    vl_xinit = MetadataKey(str)

    #: Enable Verilator VCD tracing.
    #:
    #: Type: ``bool``; input
    #:
    #: Default value: ``False``
    vl_trace = MetadataKey(bool)

    #: Filename of Verilator VCD tracing.
    #:
    #: Type: ``str``; input
    #:
    #: Default value: translated-component-name.verilator1
    vl_trace_filename = MetadataKey(str)

    #: Time scale of generated Verilator VCD.
    #:
    #: Type: ``str``; input
    #:
    #: Default value: ``'10ps'``
    vl_trace_timescale = MetadataKey(str)

    #: Cycle time of PyMTL clk pin in the generated Verilator VCD in unit of ``vl_trace_timescale``.
    #:
    #: Type: ``int``; input
    #:
    #: Default value: ``100``
    vl_trace_cycle_time = MetadataKey(int)

    #: Optional flags to be passed to the C compiler.
    #:
    #: Type: ``str``; input
    #:
    #: Default value: ``''``
    c_flags = MetadataKey(str)

    #: Optional include paths to be passed to the C compiler.
    #:
    #: Type: ``[str]``; input
    #:
    #: Default value: ``[]``
    c_include_path = MetadataKey(list)

    #: Optional source file paths to be passed to the C compiler.
    #:
    #: Type: ``[str]``; input
    #:
    #: Default value: ``[]``
    c_srcs = MetadataKey(list)

    #: Optional flags to be passed to LD.
    #:
    #: Type: ``str``; input
    #:
    #: Default value: ``''``
    ld_flags = MetadataKey(str)

    #: Optional libraries to be passed to LD (e.g., ``'-lfoo'``).
    #:
    #: Type: ``str``; input
    #:
    #: Default value: ``''``
    ld_libs = MetadataKey(str)

    # Import pass output pass data

    #: An instnace of :class:`VerilatorImportConfigs` containing the parsed options.
    #:
    #: Type: :class:`VerilatorImportConfigs`; output
    import_config = MetadataKey()

    def __call__(s, top):
        """Import the PyMTL component hierarhcy rooted at ``top``."""
        s.top = top
        if not top._dsl.constructed:
            raise VerilogImportError(
                top,
                f"please elaborate design {top} before applying the import pass!"
            )
        ret = s.traverse_hierarchy(top)
        if ret is None:
            ret = top
        else:
            ret.elaborate()
        return ret

    def traverse_hierarchy(s, m):
        c = s.__class__
        ph_pass = c.get_placeholder_pass()
        # Import can only be performed on Placeholders
        if m.has_metadata(ph_pass.enable) and m.get_metadata(ph_pass.enable):
            m.set_metadata(c.import_config, c.get_import_config()(m))
            return s.do_import(m)

        else:
            for child in m.get_child_components(repr):
                s.traverse_hierarchy(child)

    def do_import(s, m):
        try:
            imp = s.get_imported_object(m)
            if m is s.top:
                return imp
            else:
                s.top.replace_component_with_obj(m, imp)
        except AssertionError as e:
            msg = '' if e.args[0] is None else e.args[0]
            raise VerilogImportError(m, msg)

    #-----------------------------------------------------------------------
    # Backend-specific methods
    #-----------------------------------------------------------------------

    @staticmethod
    def get_backend_name():
        return "verilog"

    @staticmethod
    def get_placeholder_pass():
        return VerilogPlaceholderPass

    @staticmethod
    def get_translation_pass():
        from ..translation.VerilogTranslationPass import VerilogTranslationPass
        return VerilogTranslationPass

    @staticmethod
    def get_import_config():
        from .VerilogVerilatorImportConfigs import VerilogVerilatorImportConfigs
        return VerilogVerilatorImportConfigs

    @staticmethod
    def get_gen_mapped_port():
        return gen_mapped_ports

    #-----------------------------------------------------------------------
    # is_cached
    #-----------------------------------------------------------------------

    def is_cached(s, m, ip_cfg):
        c = s.__class__
        # Only components translated by pymtl translation passes will be cached
        try:
            is_same = m.get_metadata(c.get_translation_pass().is_same)
        except UnsetMetadataError:
            is_same = False

        # Check if the verilated model is cached
        is_source_cached = False
        obj_dir = ip_cfg.vl_mk_dir
        c_wrapper = ip_cfg.get_c_wrapper_path()
        py_wrapper = ip_cfg.get_py_wrapper_path()
        shared_lib = ip_cfg.get_shared_lib_path()
        if is_same and os.path.exists(obj_dir) and os.path.exists(c_wrapper) and \
           os.path.exists(py_wrapper) and os.path.exists(shared_lib):
            is_source_cached = True

        # Check if the configurations from the last run are the same
        is_config_cached = False
        config_file = f'pymtl_import_config_{ip_cfg.translated_top_module}.json'
        new_cfg = s.serialize_cfg(ip_cfg)
        if os.path.exists(config_file):
            with open(config_file) as fd:
                if s.is_same_cfg(json.load(fd), new_cfg):
                    is_config_cached = True

        return is_source_cached and is_config_cached, config_file, new_cfg

    #-----------------------------------------------------------------------
    # get_imported_object
    #-----------------------------------------------------------------------

    def get_imported_object(s, m):
        c = s.__class__
        ph_cfg = m.get_metadata(c.get_placeholder_pass().placeholder_config)
        ip_cfg = m.get_metadata(c.import_config)
        ip_cfg.setup_configs(m, c.get_translation_pass(),
                             c.get_placeholder_pass())

        rtype = RTLIRGetter(cache=False).get_component_ifc_rtlir(m)

        # Now we selectively unpack array of ports if they are referred to in
        # port_map
        ports = c.get_gen_mapped_port()(m, ph_cfg.port_map, ph_cfg.has_clk,
                                        ph_cfg.has_reset, ph_cfg.separator)

        cached, config_file, cfg_d = s.is_cached(m, ip_cfg)

        s.create_verilator_model(m, ph_cfg, ip_cfg, cached)

        port_cdefs = s.create_verilator_c_wrapper(m, ph_cfg, ip_cfg, ports,
                                                  cached)

        s.create_shared_lib(m, ph_cfg, ip_cfg, cached)

        symbols = s.create_py_wrapper(m, ph_cfg, ip_cfg, rtype, ports,
                                      port_cdefs, cached)

        imp = s.import_component(m, ph_cfg, ip_cfg, symbols)

        imp._ip_cfg = ip_cfg
        imp._ph_cfg = ph_cfg
        imp._ports = ports

        # Dump configuration dict to config_file
        with open(config_file, 'w') as fd:
            json.dump(cfg_d, fd, indent=4)

        return imp

    #-----------------------------------------------------------------------
    # create_verilator_model
    #-----------------------------------------------------------------------

    def create_verilator_model(s, m, ph_cfg, ip_cfg, cached):
        # Verilate module `m`.

        ip_cfg.vprint("\n=====Verilate model=====")

        if not cached:
            # Generate verilator command
            cmd = ip_cfg.create_vl_cmd()

            # Remove obj_dir directory if it already exists.
            # obj_dir is where the verilator output ( C headers and sources ) is stored
            obj_dir = ip_cfg.vl_mk_dir
            if os.path.exists(obj_dir):
                shutil.rmtree(obj_dir)

            succeeds = True

            # Try to call verilator
            try:
                ip_cfg.vprint(
                    f"Verilating {ip_cfg.translated_top_module} with command:",
                    2)
                ip_cfg.vprint(f"{cmd}", 4)
                subprocess.check_output(cmd,
                                        stderr=subprocess.STDOUT,
                                        shell=True)
            except subprocess.CalledProcessError as e:
                succeeds = False
                err_msg = e.output if not isinstance(e.output, bytes) else \
                          e.output.decode('utf-8')
                import_err_msg = \
                    f"Fail to verilate model {ip_cfg.translated_top_module}\n"\
                    f"  Verilator command:\n{indent(cmd, '  ')}\n\n"\
                    f"  Verilator output:\n{indent(wrap(err_msg), '  ')}\n"

            if not succeeds:
                raise VerilogImportError(m, import_err_msg)

            ip_cfg.vprint("Successfully verilated the given model!", 2)

        else:
            ip_cfg.vprint(
                f"{ip_cfg.translated_top_module} not verilated because it's cached!",
                2)

    #-----------------------------------------------------------------------
    # create_verilator_c_wrapper
    #-----------------------------------------------------------------------

    def create_verilator_c_wrapper(s, m, ph_cfg, ip_cfg, ports, cached):
        # Return the file name of generated C component wrapper.
        # Create a C wrapper that calls verilator C API and provides interfaces
        # that can be later called through CFFI.

        component_name = ip_cfg.translated_top_module
        vl_component_name = s._verilator_name(component_name)
        dump_vcd = int(ip_cfg.vl_trace)
        vcd_timescale = ip_cfg.vl_trace_timescale
        half_cycle_time = ip_cfg.vl_trace_cycle_time // 2
        external_trace = int(ip_cfg.vl_line_trace)
        wrapper_name = ip_cfg.get_c_wrapper_path()
        verilator_xinit_value = ip_cfg.get_vl_xinit_value()
        verilator_xinit_seed = ip_cfg.get_vl_xinit_seed()
        ip_cfg.vprint("\n=====Generate C wrapper=====")

        # Generate port declarations for the verilated model in C
        port_defs = []
        for _, v_name, port, _ in ports:
            if v_name:
                port_defs.append(s.gen_signal_decl_c(v_name, port))
        port_cdefs = copy.copy(port_defs)
        make_indent(port_defs, 2)
        port_defs = '\n'.join(port_defs)

        # Generate initialization statements for in/out ports
        port_inits = []
        for _, v_name, port, _ in ports:
            if v_name:
                port_inits.extend(s.gen_signal_init_c(v_name, port))
        make_indent(port_inits, 1)
        port_inits = '\n'.join(port_inits)

        # Fill in the C wrapper template
        with open(wrapper_name, 'w') as output:
            output.write(c_template.format(**locals()))

        ip_cfg.vprint(f"Successfully generated C wrapper {wrapper_name}!", 2)
        return port_cdefs

    #-----------------------------------------------------------------------
    # create_shared_lib
    #-----------------------------------------------------------------------

    def create_shared_lib(s, m, ph_cfg, ip_cfg, cached):
        # Return the name of compiled shared lib.

        full_name = ip_cfg.translated_top_module
        dump_vcd = ip_cfg.vl_trace
        ip_cfg.vprint("\n=====Compile shared library=====")

        if not cached:
            cmd = ip_cfg.create_cc_cmd()

            succeeds = True

            # Try to call the C compiler
            try:
                ip_cfg.vprint("Compiling shared library with command:", 2)
                ip_cfg.vprint(f"{cmd}", 4)
                subprocess.check_output(cmd,
                                        stderr=subprocess.STDOUT,
                                        shell=True,
                                        universal_newlines=True)
            except subprocess.CalledProcessError as e:
                succeeds = False
                err_msg = e.output if not isinstance(e.output, bytes) else \
                          e.output.decode('utf-8')
                import_err_msg = \
                    f"Failed to compile Verilated model into a shared library:\n"\
                    f"  C compiler command:\n{indent(cmd, '  ')}\n\n"\
                    f"  C compiler output:\n{indent(wrap(err_msg), '  ')}\n"

            if not succeeds:
                raise VerilogImportError(m, import_err_msg)

            ip_cfg.vprint(f"Successfully compiled shared library "\
                          f"{ip_cfg.get_shared_lib_path()}!", 2)

        else:
            ip_cfg.vprint("Didn't compile shared library because it's cached!",
                          2)

    #-----------------------------------------------------------------------
    # create_py_wrapper
    #-----------------------------------------------------------------------
    # Return the file name of the generated PyMTL component wrapper.

    def create_py_wrapper(s, m, ph_cfg, ip_cfg, rtype, ports, port_cdefs,
                          cached):
        ip_cfg.vprint("\n=====Generate PyMTL wrapper=====")

        wrapper_name = ip_cfg.get_py_wrapper_path()

        # Port definitions of verilated model
        make_indent(port_cdefs, 4)

        # Port definition in PyMTL style
        symbols, port_defs = s.gen_signal_decl_py(rtype)
        make_indent(port_defs, 2)

        # Set upblk inputs and outputs
        set_comb_input, structs_input = s.gen_comb_input(ports, symbols)
        set_comb_output, structs_output = s.gen_comb_output(ports, symbols)
        make_indent(structs_input, 2)
        make_indent(structs_output, 2)
        make_indent(set_comb_input, 3)
        make_indent(set_comb_output, 3)

        # Line trace
        line_trace = s.gen_line_trace_py(ports)

        # Internal line trace
        in_line_trace = s.gen_internal_line_trace_py(ports)

        # External trace function definition
        if ip_cfg.vl_line_trace:
            external_trace_c_def = f'void trace( V{ip_cfg.translated_top_module}_t *, char * );'
        else:
            external_trace_c_def = ''

        # Fill in the python wrapper template
        with open(wrapper_name, 'w') as output:
            py_wrapper = py_template.format(
              component_name        = ip_cfg.translated_top_module,
              has_clk               = int(ph_cfg.has_clk),
              clk                   = 'inv_clk' if not ph_cfg.has_clk else \
                                      next(filter(lambda x: x[0][0]=='clk', ports))[1],
              lib_file              = ip_cfg.get_shared_lib_path(),
              port_cdefs            = ('  '*4+'\n').join( port_cdefs ),
              port_defs             = '\n'.join( port_defs ),
              structs_input         = '\n'.join( structs_input ),
              structs_output        = '\n'.join( structs_output ),
              set_comb_input        = '\n'.join( set_comb_input ),
              set_comb_output       = '\n'.join( set_comb_output ),
              line_trace            = line_trace,
              in_line_trace         = in_line_trace,
              dump_vcd              = int(ip_cfg.vl_trace),
              has_vl_trace_filename = bool(ip_cfg.vl_trace_filename),
              vl_trace_filename     = ip_cfg.vl_trace_filename,
              external_trace        = int(ip_cfg.vl_line_trace),
              trace_c_def           = external_trace_c_def,
            )
            output.write(py_wrapper)

        ip_cfg.vprint(f"Successfully generated PyMTL wrapper {wrapper_name}!",
                      2)
        return symbols

    #-----------------------------------------------------------------------
    # import_component
    #-----------------------------------------------------------------------
    # Return the PyMTL component imported from `wrapper_name`.v.

    def import_component(s, m, ph_cfg, ip_cfg, symbols):
        ip_cfg.vprint("=====Create python object=====")

        component_name = ip_cfg.translated_top_module
        # Get the name of the wrapper Python module
        wrapper_name = ip_cfg.get_py_wrapper_path()
        wrapper = wrapper_name.split('.')[0]

        # Add CWD to sys.path so we can import from the current directory
        if not os.getcwd() in sys.path:
            sys.path.append(os.getcwd())

        # Check linecache in case the wrapper file has been modified
        linecache.checkcache()

        # Import the component from python wrapper

        if wrapper in sys.modules:
            # Reload the wrapper module in case the user has updated the wrapper
            reload(sys.modules[wrapper])
        else:
            # importlib.import_module inserts the wrapper module into sys.modules
            importlib.import_module(wrapper)

        # Try to access the top component class from the wrapper module
        try:
            imp_class = getattr(sys.modules[wrapper], component_name)
        except AttributeError as e:
            raise VerilogImportError(
                m,
                f"internal error: PyMTL wrapper {wrapper_name} does not have "
                f"top component {component_name}!") from e

        imp = imp_class()
        ip_cfg.vprint(
            f"Successfully created python object of {component_name}!", 2)

        # Update the global namespace of `construct` so that the struct and interface
        # classes defined previously can still be used in the imported model.
        imp.construct.__globals__.update(symbols)

        ip_cfg.vprint("Import succeeds!")
        return imp

    #-------------------------------------------------------------------------
    # Serialize and compare configurations
    #-------------------------------------------------------------------------

    def serialize_cfg(s, ip_cfg):
        d = {}
        s._volatile_configs = [
            'vl_line_trace',
            'vl_coverage',
            'vl_line_coverage',
            'vl_toggle_coverage',
            'vl_mk_dir',
            'vl_enable_assert',
            'vl_opt_level',
            'vl_unroll_count',
            'vl_unroll_stmts',
            'vl_W_lint',
            'vl_W_style',
            'vl_W_fatal',
            'vl_Wno_list',
            'vl_xinit',
            'vl_trace',
            'vl_trace_timescale',
            'vl_trace_cycle_time',
            'c_flags',
            'c_include_path',
            'c_srcs',
            'ld_flags',
            'ld_libs',
        ]
        d['ImportPassName'] = 'VerilogVerilatorImportPass'
        for cfg in s._volatile_configs:
            d[cfg] = copy.deepcopy(getattr(ip_cfg, cfg))

        return d

    def is_same_cfg(s, prev, new):
        _volatile_configs = copy.copy(s._volatile_configs)
        _volatile_configs.append('ImportPassName')
        return all(prev[cfg] == new[cfg] for cfg in _volatile_configs)

    #-------------------------------------------------------------------------
    # gen_signal_decl_c
    #-------------------------------------------------------------------------
    # Return C variable declaration of `port`.

    def gen_signal_decl_c(s, name, port):

        c_dim = s._get_c_dim(port)
        nbits = s._get_c_nbits(port)
        UNSIGNED_8 = 'unsigned char'
        UNSIGNED_16 = 'unsigned short'
        UNSIGNED_32 = 'unsigned int'
        if sys.maxsize > 2**32:
            UNSIGNED_64 = 'unsigned long'
        else:
            UNSIGNED_64 = 'unsigned long long'
        if nbits <= 8: data_type = UNSIGNED_8
        elif nbits <= 16: data_type = UNSIGNED_16
        elif nbits <= 32: data_type = UNSIGNED_32
        elif nbits <= 64: data_type = UNSIGNED_64
        else: data_type = UNSIGNED_32
        name = s._verilator_name(name)
        return f'{data_type} * {name}{c_dim};'

    #-------------------------------------------------------------------------
    # gen_signal_init_c
    #-------------------------------------------------------------------------
    # Return C port variable initialization.

    def gen_signal_init_c(s, name, port):

        ret = []
        c_dim = s._get_c_dim(port)
        nbits = s._get_c_nbits(port)
        deference = '&' if nbits <= 64 else ''
        name = s._verilator_name(name)

        if c_dim:
            n_dim_size = s._get_c_n_dim(port)
            sub = ""
            for_template = \
      """\
for ( int i_{idx} = 0; i_{idx} < {dim_size}; i_{idx}++ )
"""
            assign_template = \
      """\
m->{name}{sub} = {deference}model->{name}{sub};
"""

            for idx, dim_size in enumerate(n_dim_size):
                ret.append(for_template.format(**locals()))
                sub += f"[i_{idx}]"

            ret.append(assign_template.format(**locals()))

            # Indent the for loop
            for start, dim_size in enumerate(n_dim_size):
                for idx in range(start + 1, len(n_dim_size) + 1):
                    ret[idx] = "  " + ret[idx]

        else:
            ret.append(f'm->{name} = {deference}model->{name};')

        return ret

    #-------------------------------------------------------------------------
    # gen_signal_decl_py
    #-------------------------------------------------------------------------
    # Return the PyMTL definition of all interface ports of `rtype`.

    def gen_signal_decl_py(s, rtype):

        #-----------------------------------------------------------------------
        # Methods that generate signal declarations
        #-----------------------------------------------------------------------

        def gen_dtype_str(symbols, dtype):
            if isinstance(dtype, rdt.Vector):
                nbits = dtype.get_length()
                Bits_name = f"Bits{nbits}"
                if Bits_name not in symbols and nbits >= 256:
                    Bits_class = mk_bits(nbits)
                    symbols.update({Bits_name: Bits_class})
                return f'Bits{dtype.get_length()}'
            elif isinstance(dtype, rdt.Struct):
                # It is possible to reuse the existing struct class because its __init__
                # can be called without arguments.
                name, cls = dtype.get_name(), dtype.get_class()
                if name not in symbols:
                    symbols.update({name: cls})
                return name
            else:
                assert False, f"unrecognized data type {dtype}!"

        def gen_port_decl_py(ports):
            symbols, decls = {}, []
            for id_, _port in ports:
                if id_ not in ['clk', 'reset']:
                    if isinstance(_port, rt.Array):
                        n_dim = _port.get_dim_sizes()
                        rhs = "{direction}( {dtype} )"
                        port = _port.get_sub_type()
                        _n_dim = copy.copy(n_dim)
                        _n_dim.reverse()
                        for length in _n_dim:
                            rhs = f"[ {rhs} for _ in range({length}) ]"
                    else:
                        rhs = "{direction}( {dtype} )"
                        port = _port
                    direction = s._get_direction(port)
                    dtype = gen_dtype_str(symbols, port.get_dtype())
                    rhs = rhs.format(**locals())
                    decls.append(f"s.{id_} = {rhs}")
            return symbols, decls

        def gen_ifc_decl_py(ifcs):
            def gen_ifc_str(symbols, ifc):
                def _get_arg_str(name, obj):
                    # Support common python types and Bits/BitStruct
                    if isinstance(obj, (int, bool, str)):
                        return str(obj)
                    elif obj == None:
                        return 'None'
                    elif isinstance(obj, Bits):
                        nbits = obj.nbits
                        value = int(obj)
                        Bits_name = f"Bits{nbits}"
                        Bits_arg_str = f"{Bits_name}( {value} )"
                        if Bits_name not in symbols and nbits >= 256:
                            Bits_class = mk_bits(nbits)
                            symbols.update({Bits_name: Bits_class})
                        return Bits_arg_str
                    elif is_bitstruct_inst(obj):
                        raise TypeError(
                            "Do you really want to pass in an instance of "
                            "a BitStruct? Contact PyMTL developers!")
                        # This is hacky: we don't know how to construct an object that
                        # is the same as `obj`, but we do have the object itself. If we
                        # add `obj` to the namespace of `construct` everything works fine
                        # but the user cannot tell what object is passed to the constructor
                        # just from the code.
                        # Do not use a double underscore prefix because that will be
                        # interpreted differently by the Python interpreter
                        # bs_name = ("_" if name[0] != "_" else "") + name + "_obj"
                        # if bs_name not in symbols:
                        # symbols.update( { bs_name : obj } )
                        # return bs_name
                    elif isinstance(obj, type) and issubclass(obj, Bits):
                        nbits = obj.nbits
                        Bits_name = f"Bits{nbits}"
                        if Bits_name not in symbols and nbits >= 256:
                            Bits_class = mk_bits(nbits)
                            symbols.update({Bits_name: Bits_class})
                        return Bits_name
                    elif is_bitstruct_class(obj):
                        BitStruct_name = obj.__name__
                        if BitStruct_name not in symbols:
                            symbols.update({BitStruct_name: obj})
                        return BitStruct_name
                    # FIXME formalize this
                    elif isinstance(obj, type) and hasattr(
                            obj, 'req') and hasattr(obj, 'resp'):
                        symbols.update({obj.__name__: obj})
                        return obj.__name__
                    raise TypeError(
                        f"Interface constructor argument {obj} is not an int/Bits/BitStruct/TypeTuple!"
                    )

                name, cls = ifc.get_name(), ifc.get_class()
                if name not in symbols:
                    symbols.update({name: cls})
                arg_list = []
                args = ifc.get_args()
                for idx, obj in enumerate(args[0]):
                    arg_list.append(_get_arg_str(f"_ifc_arg{idx}", obj))
                for arg_name, arg_obj in args[1].items():
                    arg_list.append(
                        f"{arg_name} = {_get_arg_str( arg_name, arg_obj )}")
                return name, ', '.join(arg_list)

            symbols, decls = {}, []
            for id_, ifc in ifcs:
                if isinstance(ifc, rt.Array):
                    n_dim = ifc.get_dim_sizes()
                    rhs = "{ifc_class}({ifc_params})"
                    _ifc = ifc.get_sub_type()
                    _n_dim = copy.copy(n_dim)
                    _n_dim.reverse()
                    for length in _n_dim:
                        rhs = f"[ {rhs} for _ in range({length}) ]"
                else:
                    rhs = "{ifc_class}({ifc_params})"
                    _ifc = ifc
                ifc_class, ifc_params = gen_ifc_str(symbols, _ifc)
                if ifc_params:
                    ifc_params = " " + ifc_params + " "
                rhs = rhs.format(**locals())
                decls.append(f"s.{id_} = {rhs}")
            return symbols, decls

        #-----------------------------------------------------------------------
        # Method gen_signal_decl_py
        #-----------------------------------------------------------------------

        ports = rtype.get_ports_packed()
        ifcs = rtype.get_ifc_views_packed()

        p_symbols, p_decls = gen_port_decl_py(ports)
        i_symbols, i_decls = gen_ifc_decl_py(ifcs)

        return {**p_symbols, **i_symbols}, p_decls + i_decls

    #-----------------------------------------------------------------------
    # Methods that generate python signal writes
    #-----------------------------------------------------------------------

    def _gen_vector_write(s, d, lhs, rhs, dtype, pos):
        nbits = dtype.get_length()
        l, r = pos, pos + nbits
        if d == 'i':
            ret = [f"{rhs}[{l}:{r}] @= {lhs}"]
        else:
            ret = [f"{lhs} @= {rhs}[{l}:{r}]"]
        return ret, r

    def _gen_struct_write(s, d, lhs, rhs, dtype, pos):
        ret = []
        all_properties = reversed(list(dtype.get_all_properties().items()))
        for name, field in all_properties:
            _ret, pos = s._gen_write_dispatch(d, f"{lhs}.{name}", rhs, field,
                                              pos)
            ret.extend(_ret)
        return ret, pos

    def _gen_packed_array_write(s, d, lhs, rhs, dtype, n_dim, pos):
        if not n_dim:
            return s._gen_write_dispatch(d, lhs, rhs, dtype, pos)
        # Recursively generate array
        ret = []
        for idx in range(n_dim[0]):
            _ret, pos = s._gen_packed_array_write(d, f"{lhs}[{idx}]", rhs,
                                                  dtype, n_dim[1:], pos)
            ret.extend(_ret)
        return ret, pos

    def _gen_write_dispatch(s, d, lhs, rhs, dtype, pos):
        if isinstance(dtype, rdt.Vector):
            return s._gen_vector_write(d, lhs, rhs, dtype, pos)
        elif isinstance(dtype, rdt.Struct):
            return s._gen_struct_write(d, lhs, rhs, dtype, pos)
        elif isinstance(dtype, rdt.PackedArray):
            n_dim = dtype.get_dim_sizes()
            sub_dtype = dtype.get_sub_dtype()
            return s._gen_packed_array_write(d, lhs, rhs, sub_dtype, n_dim,
                                             pos)
        assert False, f"unrecognized data type {dtype}!"

    #-------------------------------------------------------------------------
    # gen_comb_input
    #-------------------------------------------------------------------------

    def gen_port_vector_input(s, lhs, rhs, mangled_rhs, dtype, symbols):
        dtype_nbits = dtype.get_length()
        blocks = [
            f's.{mangled_rhs} = Wire( {s._gen_bits_decl(dtype_nbits)} )',
            '@update', f'def isignal_{mangled_rhs}():',
            f'  s.{mangled_rhs} @= {rhs}'
        ]
        set_comb = (s._gen_ref_write(lhs, 's.' + mangled_rhs, dtype_nbits))
        return set_comb, blocks

    def gen_port_struct_input(s, lhs, rhs, mangled_rhs, dtype, symbols):
        dtype_nbits = dtype.get_length()
        # If the top-level signal is a struct, we add the datatype to symbol?
        dtype_name = dtype.get_class().__name__
        if dtype_name not in symbols:
            symbols[dtype_name] = dtype.get_class()

        blocks = [
            f's.{mangled_rhs} = Wire( {s._gen_bits_decl(dtype_nbits)} )',
            '@update', f'def istruct_{mangled_rhs}():'
        ]

        # We write each struct field to tmp
        upblk_content, pos = s._gen_struct_write('i', rhs, 's.' + mangled_rhs,
                                                 dtype, 0)
        assert pos == dtype_nbits
        make_indent(upblk_content, 1)
        blocks += upblk_content

        # We don't create a new struct if we are copying values from pymtl
        # land to verilator, i.e. this port is the input to the imported
        # component.
        # At the end, we write tmp to the corresponding CFFI variable
        set_comb = s._gen_ref_write(lhs, 's.' + mangled_rhs, dtype_nbits)
        return set_comb, blocks

    def gen_port_input(s, lhs, rhs, pnames, dtype, symbols):
        rhs = rhs.format(next(pnames))

        # We always name mangle now
        mangled_rhs = s._pymtl_name_mangle(rhs)

        if isinstance(dtype, rdt.Vector):
            return s.gen_port_vector_input(lhs, rhs, mangled_rhs, dtype,
                                           symbols)

        elif isinstance(dtype, rdt.Struct):
            return s.gen_port_struct_input(lhs, rhs, mangled_rhs, dtype,
                                           symbols)

        else:
            assert False, f"unrecognized data type {dtype}!"

    def gen_port_array_input(s, lhs, rhs, pnames, dtype, index, n_dim,
                             symbols):
        if not n_dim:
            return s.gen_port_input(lhs, rhs, pnames, dtype, symbols)
        else:
            set_comb, structs = [], []
            for idx in range(n_dim[0]):
                _lhs = f"{lhs}[{idx}]"
                if index == 0:
                    _rhs = f"{rhs}[{idx}]"
                    _index = index
                else:
                    _rhs = f"{rhs}"
                    _index = index - 1
                _set_comb, _structs = s.gen_port_array_input(
                    _lhs, _rhs, pnames, dtype, _index, n_dim[1:], symbols)
                set_comb += _set_comb
                structs += _structs
            return set_comb, structs

    def gen_comb_input(s, packed_ports, symbols):
        set_comb, structs = [], []
        # Read all input ports ( except for 'clk' ) from component ports into
        # the verilated model. We do NOT want `clk` signal to be read into
        # the verilated model because only the sequential update block of
        # the imported component should manipulate it.

        for _pnames, vname, rtype, port_idx in packed_ports:
            if isinstance(rtype, rt.Array):
                n_dim = rtype.get_dim_sizes()
            else:
                n_dim = []
            repeats = reduce(lambda a, b: a * b, n_dim[port_idx:], 1)
            pnames = [name for name in _pnames for _ in range(repeats)]
            pnames_iter = cycle(pnames)
            p_n_dim, p_rtype = get_rtype(rtype)
            if s._get_direction(
                    p_rtype) == 'InPort' and pnames[0] != 'clk' and vname:
                dtype = p_rtype.get_dtype()
                lhs = "_ffi_m." + s._verilator_name(vname)
                rhs = "s.{}"
                idx = port_idx
                _set_comb, _structs = s.gen_port_array_input(
                    lhs, rhs, pnames_iter, dtype, idx, p_n_dim, symbols)
                set_comb += _set_comb
                structs += _structs

        return set_comb, structs

    #-------------------------------------------------------------------------
    # gen_comb_output
    #-------------------------------------------------------------------------

    def gen_port_vector_output(s, lhs, mangled_lhs, rhs, dtype, symbols):
        dtype_nbits = dtype.get_length()
        blocks = [
            f's.{mangled_lhs} = Wire( {s._gen_bits_decl(dtype_nbits)} )',
            '@update', f'def osignal_{mangled_lhs}():',
            f'  {lhs} @= s.{mangled_lhs}'
        ]

        set_comb = s._gen_ref_read('s.' + mangled_lhs, rhs, dtype_nbits, '@=')
        return set_comb, blocks

    def gen_port_struct_output(s, lhs, mangled_lhs, rhs, dtype, symbols):
        dtype_nbits = dtype.get_length()
        # If the top-level signal is a struct, we add the datatype to symbol?
        dtype_name = dtype.get_class().__name__
        if dtype_name not in symbols:
            symbols[dtype_name] = dtype.get_class()

        blocks = [
            f's.{mangled_lhs} = Wire( {s._gen_bits_decl(dtype_nbits)} )',
            '@update', f'def ostruct_{mangled_lhs}():'
        ]

        # We create a long Bits object to accept CFFI value for struct
        # the temporary wire name

        # We create a new struct if we are copying values from verilator
        # world to pymtl land and send it out through the output of this
        # component
        upblk_content = [f"{lhs} @= {dtype_name}()"]
        body, pos = s._gen_struct_write('o', lhs, 's.' + mangled_lhs, dtype, 0)
        assert pos == dtype.get_length()

        upblk_content += body
        make_indent(upblk_content, 1)

        blocks += upblk_content

        # We create a long Bits object tmp first
        # Then we load the full Bits to tmp
        set_comb = s._gen_ref_read('s.' + mangled_lhs, rhs, dtype_nbits, '@=')
        return set_comb, blocks

    def gen_port_output(s, lhs, pnames, rhs, dtype, symbols):
        lhs = lhs.format(next(pnames))

        mangled_lhs = s._pymtl_name_mangle(lhs)

        if isinstance(dtype, rdt.Vector):
            return s.gen_port_vector_output(lhs, mangled_lhs, rhs, dtype,
                                            symbols)
        elif isinstance(dtype, rdt.Struct):
            return s.gen_port_struct_output(lhs, mangled_lhs, rhs, dtype,
                                            symbols)
        else:
            assert False, f"unrecognized data type {dtype}!"

    def gen_port_array_output(s, lhs, pnames, rhs, dtype, index, n_dim,
                              symbols):
        if not n_dim:
            return s.gen_port_output(lhs, pnames, rhs, dtype, symbols)
        else:
            set_comb, structs = [], []
            for idx in range(n_dim[0]):
                if index == 0:
                    _lhs = f"{lhs}[{idx}]"
                    _index = index
                else:
                    _lhs = f"{lhs}"
                    _index = index - 1
                _rhs = f"{rhs}[{idx}]"
                _set_comb, _structs = s.gen_port_array_output(
                    _lhs, pnames, _rhs, dtype, _index, n_dim[1:], symbols)
                set_comb += _set_comb
                structs += _structs
            return set_comb, structs

    def gen_comb_output(s, packed_ports, symbols):
        set_comb, structs = [], []
        for _pnames, vname, rtype, port_idx in packed_ports:
            if isinstance(rtype, rt.Array):
                n_dim = rtype.get_dim_sizes()
            else:
                n_dim = []
            repeats = reduce(lambda a, b: a * b, n_dim[port_idx:], 1)
            pnames = [name for name in _pnames for _ in range(repeats)]
            pnames_iter = cycle(pnames)
            p_n_dim, p_rtype = get_rtype(rtype)
            if s._get_direction(rtype) == 'OutPort':
                dtype = p_rtype.get_dtype()
                lhs = "s.{}"
                rhs = "_ffi_m." + s._verilator_name(vname)
                idx = port_idx
                _set_comb, _structs = s.gen_port_array_output(
                    lhs, pnames_iter, rhs, dtype, idx, p_n_dim, symbols)
                set_comb += _set_comb
                structs += _structs
        return set_comb, structs

    #-------------------------------------------------------------------------
    # gen_line_trace_py
    #-------------------------------------------------------------------------
    # Return the line trace method body that shows all interface ports.

    def gen_line_trace_py(s, packed_ports):
        template = '{0}={{s.{0}}},'
        trace_string = ''
        for pnames, _, _, _ in packed_ports:
            for pname in pnames:
                trace_string += ' ' + template.format(pname)
        return f"      return f'{trace_string}'"

    #-------------------------------------------------------------------------
    # gen_internal_line_trace_py
    #-------------------------------------------------------------------------
    # Return the line trace method body that shows all CFFI ports.
    # Now that there could be multiple pnames that correspond to one vname,
    # I'm not sure how to generate internal line trace... maybe we should
    # deprecate internal_line_trace since it's not used by many anyways?

    def gen_internal_line_trace_py(s, packed_ports):
        # ret = [ '_ffi_m = s._ffi_m', 'lt = ""' ]
        # template = \
        #   "lt += '{vname} = {{}}, '.format(full_vector(s.{pname}, _ffi_m.{vname}))"
        # for pname, vname, port in packed_ports:
        #   if vname:
        #     pname = s._verilator_name(pname)
        #     vname = s._verilator_name(vname)
        #     ret.append( template.format(**locals()) )
        # ret.append( 'return lt' )
        # make_indent( ret, 2 )
        # return '\n'.join( ret )
        return "    return ''"

    #=========================================================================
    # Helper functions
    #=========================================================================

    def _verilator_name(s, name):
        # TODO: PyMTL translation should generate dollar-sign-free Verilog source
        # code. Verify that this replacement rule here is not necessary.
        return name.replace('__', '___05F').replace('$', '__024')

    def _pymtl_name_mangle(s, name):
        return name.replace('.', '_DOT_').replace('[',
                                                  '_LB_').replace(']', '_RB_')

    def _get_direction(s, port):
        if isinstance(port, rt.Port):
            d = port.get_direction()
        elif isinstance(port, rt.Array):
            d = port.get_sub_type().get_direction()
        else:
            assert False, f"{port} is not a port or array of ports!"
        if d == 'input':
            return 'InPort'
        elif d == 'output':
            return 'OutPort'
        else:
            assert False, f"unrecognized direction {d}!"

    def _get_c_n_dim(s, port):
        if isinstance(port, rt.Array):
            return port.get_dim_sizes()
        else:
            return []

    def _get_c_dim(s, port):
        return "".join(f"[{i}]" for i in s._get_c_n_dim(port))

    def _get_c_nbits(s, port):
        if isinstance(port, rt.Array):
            dtype = port.get_sub_type().get_dtype()
        else:
            dtype = port.get_dtype()
        return dtype.get_length()

    def _gen_ref_write(s, lhs, rhs, nbits, equal='='):
        if nbits <= 64:
            return [f"{lhs}[0] {equal} int({rhs})"]
        else:
            ret = []
            ITEM_BITWIDTH = 32
            num_assigns = (nbits - 1) // ITEM_BITWIDTH + 1
            for idx in range(num_assigns):
                l = ITEM_BITWIDTH * idx
                r = l + ITEM_BITWIDTH if l + ITEM_BITWIDTH <= nbits else nbits
                ret.append(f"{lhs}[{idx}] {equal} int({rhs}[{l}:{r}])")
            return ret

    def _gen_ref_read(s, lhs, rhs, nbits, equal='='):
        if nbits <= 64:
            return [f"{lhs} {equal} Bits{nbits}({rhs}[0])"]
        else:
            ret = []
            ITEM_BITWIDTH = 32
            num_assigns = (nbits - 1) // ITEM_BITWIDTH + 1
            for idx in range(num_assigns):
                l = ITEM_BITWIDTH * idx
                r = l + ITEM_BITWIDTH if l + ITEM_BITWIDTH <= nbits else nbits
                _nbits = r - l
                ret.append(
                    f"{lhs}[{l}:{r}] {equal} Bits{_nbits}({rhs}[{idx}])")
            return ret

    def _gen_bits_decl(s, nbits):
        if nbits <= 256:
            return f'Bits{nbits}'
        else:
            return f'mk_bits({nbits})'
Esempio n. 7
0
class VerilogTranslationPass(BasePass):
    """Translate a PyMTL component hierarchy into Verilog."""

    # Translation pass input pass data

    #: Enable translation on a component.
    #:
    #: Type: ``bool``; input
    #:
    #: Default value: ``False``
    enable = MetadataKey(bool)

    #: Specify the filename of the translated source file.
    #:
    #: Type: ``str``; input
    #:
    #: Default value: value of ``explicit_module_name``
    explicit_file_name = MetadataKey(str)

    #: Specify the translated name of the component.
    #: Note that this option only works for the top level component.
    #:
    #: Type: ``str``; input
    #:
    #: Default value: component class name concatenated with parameters
    explicit_module_name = MetadataKey(str)

    #: Wrap component translation result within \`ifndef SYNTHESIS
    #: Enabling this option effectively removes the component from
    #: the hierarchy during logic synthesis.
    #:
    #: Type: ``bool``; input
    #:
    #: Default value: ``False``
    no_synthesis = MetadataKey(bool)

    #: In the translated source, wrap the clk port connection of the
    #: component within \`ifndef SYNTHESIS.
    #: This option can only be enabled while ``no_synthesis`` is ``True``.
    #: Enabling this option effectively removes the clk pin of the component
    #: during logic synthesis.
    #:
    #: Type: ``bool``; input
    #:
    #: Default value: ``False``
    no_synthesis_no_clk = MetadataKey(bool)

    #: In the translated source, wrap the reset port connection of the
    #: component within \`ifndef SYNTHESIS.
    #: This option can only be enabled while ``no_synthesis`` is ``True``.
    #: Enabling this option effectively removes the reset pin of the component
    #: during logic synthesis.
    #:
    #: Type: ``bool``; input
    #:
    #: Default value: ``False``
    no_synthesis_no_reset = MetadataKey(bool)

    # Translation pass output pass data

    #: An instance of :class:`TranslationConfigs` that contains the parsed options.
    #:
    #: Type: ``TranslationConfigs``; output
    translate_config = MetadataKey()

    #: Whether or not the translated result is the same as the existing output file.
    #:
    #: Type: ``bool``; output
    is_same = MetadataKey(bool)

    #: A reference of the translator called during translation.
    #:
    #: Type: output
    translator = MetadataKey()

    #: Whether or not the component has been translated.
    #:
    #: Type: ``bool``; output
    translated = MetadataKey(bool)

    #: Filename of the translation result.
    #:
    #: Type: ``str``; output
    translated_filename = MetadataKey(str)

    #: Top level module name in the translation result.
    #:
    #: Type: ``str``; output
    translated_top_module = MetadataKey(str)

    def __call__(s, top):
        """Translate a PyMTL component hierarhcy rooted at ``top``."""
        s.top = top
        s.translator = VTranslator(s.top)
        s.traverse_hierarchy(top)

    def get_translation_config(s):
        from pymtl3.passes.backends.verilog.translation.VerilogTranslationConfigs import (
            VerilogTranslationConfigs, )
        return VerilogTranslationConfigs

    def gen_tr_cfgs(s, m):
        tr_cfgs = {}

        def traverse(m):
            nonlocal tr_cfgs
            tr_cfgs[m] = s.get_translation_config()(m)
            for _m in m.get_child_components(repr):
                traverse(_m)

        traverse(m)
        return tr_cfgs

    def traverse_hierarchy(s, m):
        c = s.__class__

        if m.has_metadata(c.enable) and m.get_metadata(c.enable):
            m.set_metadata(c.translate_config, s.gen_tr_cfgs(m))
            s.translator.translate(m, m.get_metadata(c.translate_config))

            module_name = s.translator._top_module_full_name

            if m.has_metadata( c.explicit_file_name ) and \
               m.get_metadata( c.explicit_file_name ):
                fname = m.get_metadata(c.explicit_file_name)
                if '.v' in fname:
                    filename = fname.split('.v')[0]
                elif '.sv' in fname:
                    filename = fname.split('.sv')[0]
                else:
                    filename = fname

                output_file = filename + '.v'
                temporary_file = filename + '.v.tmp'

                # First write the file to a temporary file
                is_same = False
                with open(temporary_file, 'w') as output:
                    output.write(s.translator.hierarchy.src)
                    output.flush()
                    os.fsync(output)
                    output.close()

                # `is_same` is set if there exists a file that has the same filename as
                # `output_file`, and that file is the same as the temporary file
                if (os.path.exists(output_file)):
                    is_same = verilog_cmp(temporary_file, output_file)

                # Rename the temporary file to the output file
                os.rename(temporary_file, output_file)

                # Expose some attributes about the translation process
                m.set_metadata(c.is_same, is_same)
                m.set_metadata(c.translator, s.translator)
                m.set_metadata(c.translated, True)
                m.set_metadata(c.translated_filename, output_file)
                m.set_metadata(c.translated_top_module, module_name)

            else:
                filename = f"{module_name}__pickled"

            output_file = filename + '.v'
            temporary_file = filename + '.v.tmp'

            # First write the file to a temporary file
            is_same = False
            with open(temporary_file, 'w') as output:
                output.write(s.translator.hierarchy.src)
                output.flush()
                os.fsync(output)
                output.close()

            # `is_same` is set if there exists a file that has the same filename as
            # `output_file`, and that file is the same as the temporary file
            if (os.path.exists(output_file)):
                is_same = verilog_cmp(temporary_file, output_file)

            # Rename the temporary file to the output file
            os.rename(temporary_file, output_file)

            # Expose some attributes about the translation process
            m.set_metadata(c.is_same, is_same)
            m.set_metadata(c.translator, s.translator)
            m.set_metadata(c.translated, True)
            m.set_metadata(c.translated_filename, output_file)
            m.set_metadata(c.translated_top_module, module_name)

        else:
            for child in m.get_child_components(repr):
                s.traverse_hierarchy(child)
Esempio n. 8
0
class VerilogPlaceholderPass(PlaceholderPass):

    # Placeholder pass public pass data

    #: A dict that maps the module parameters to their values.
    #:
    #: Type: ``{ str : int }``; input
    #:
    #: Default value: ``{}``
    params = MetadataKey(dict)

    #: A dict that maps the PyMTL port name to the external source port name.
    #:
    #: Type: ``{ port : str }``; input
    #:
    #: Default value: ``{}``
    port_map = MetadataKey(dict)

    #: Top level module name in the external source file.
    #:
    #: Type: ``str``; input
    #:
    #: Default value: PyMTL component class name
    top_module = MetadataKey(str)

    #: Path to the external source file.
    #:
    #: Type: ``str``; input
    #:
    #: Default value: <top_module>.v
    src_file = MetadataKey(str)

    #: List of Verilog source file paths.
    #:
    #: Type: ``[str]``; input
    #:
    #: Default value: []
    v_flist = MetadataKey(list)

    #: List of Verilog library file paths. These files will be added to the
    #: beginning of the pikcling result.
    #:
    #: Type: ``[str]``; input
    #:
    #: Default value: []
    v_libs = MetadataKey(list)

    #: List of Verilog include directory paths.
    #:
    #: Type: ``[str]``; input
    #:
    #: Default value: []
    v_include = MetadataKey(list)

    #: Separator string used by name-mangling of interfaces and arrays.
    #: For example, with the default value, ``s.ifc.msg`` will be mangled to ``ifc_msg``.
    #:
    #: Type: ``str``; input
    #:
    #: Default value: ``'_'``
    separator = MetadataKey(str)

    @staticmethod
    def get_placeholder_config():
        from pymtl3.passes.backends.verilog.VerilogPlaceholderConfigs import (
            VerilogPlaceholderConfigs, )
        return VerilogPlaceholderConfigs

    # Override
    def visit_placeholder(s, m):
        c = s.__class__
        super().visit_placeholder(m)
        irepr = RTLIRGetter(cache=False).get_component_ifc_rtlir(m)

        s.setup_default_configs(m, irepr)
        cfg = m.get_metadata(c.placeholder_config)

        if cfg.enable:
            s.check_valid(m, cfg, irepr)
            s.pickle(m, cfg, irepr)

    def setup_default_configs(s, m, irepr):
        c = s.__class__
        cfg = m.get_metadata(c.placeholder_config)

        if cfg.enable:
            # If top_module is unspecified, infer it from the component and its
            # parameters. Note we need to make sure the infered top_module matches
            # the default translation result.
            if not cfg.top_module:
                cfg.top_module = irepr.get_name()

            # If the placeholder has parameters, use the mangled unique component
            # name. Otherwise use {class_name}_noparam to avoid duplicated defs.
            has_params = bool(irepr.get_params()) or bool(cfg.params)
            if has_params:
                cfg.pickled_top_module = get_component_unique_name(irepr)
            else:
                cfg.pickled_top_module = f"{irepr.get_name()}_noparam"

            # Only try to infer the name of Verilog source file if both
            # flist and the source file are not specified.
            if not cfg.src_file and not cfg.v_flist:
                parent_dir = os.path.dirname(inspect.getfile(m.__class__))
                cfg.src_file = f"{parent_dir}{os.sep}{cfg.top_module}.v"

            # Pickled file name should always be the same as the top level
            # module name.
            cfg.pickled_source_file = f"{cfg.pickled_top_module}__pickled.v"

            # What is the original file/flist of the pickled source file?
            if cfg.src_file:
                cfg.pickled_orig_file = cfg.src_file
            else:
                cfg.pickled_orig_file = cfg.v_flist

            # The unique placeholder name
            cfg.orig_comp_name = get_component_unique_name(irepr)

            # The `ifdef dependency guard is a function of the placeholder
            # class name
            cfg.dependency_guard_symbol = m.__class__.__name__.upper()

            # The `ifdef placeholder guard is a function of the placeholder
            # wrapper name
            cfg.wrapper_guard_symbol = cfg.pickled_top_module.upper()

            # Scan through src_file to check for clk and reset
            if cfg.is_default('has_clk'):
                cfg.has_clk = s._has_pin(cfg.src_file, 'input', 1, 'clk')

            if cfg.is_default('has_reset'):
                cfg.has_reset = s._has_pin(cfg.src_file, 'input', 1, 'reset')

            # By default, the separator of placeholders is single underscore
            cfg.separator = '_'

            # Look for pymtl.ini starting from the directory that includes the def
            # of class m.__class__
            auto_prefix = s._get_auto_prefix(m)

            # Apply auto_prefix to top_module
            cfg.top_module = auto_prefix + cfg.top_module

    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 pickle(s, m, cfg, irepr):
        pickled_dependency = s._get_v_lib_files(m, cfg, irepr)
        pickled_dependency += s._get_dependent_verilog_modules(m, cfg, irepr)
        pickled_wrapper, tplt = s._gen_verilog_wrapper(m, cfg, irepr)

        pickled_dependency_source = (
            f"//***********************************************************\n"
            f"// Pickled source file of placeholder {cfg.orig_comp_name}\n"
            f"//***********************************************************\n"
            f"\n"
            f"//-----------------------------------------------------------\n"
            f"// Dependency of placeholder {m.__class__.__name__}\n"
            f"//-----------------------------------------------------------\n"
            f"\n"
            f"`ifndef {cfg.dependency_guard_symbol}\n"
            f"`define {cfg.dependency_guard_symbol}\n"
            f"\n"
            f"{pickled_dependency}"
            f"\n"
            f"`endif /* {cfg.dependency_guard_symbol} */\n")

        pickled_wrapper_source_tplt = (
            f"\n"
            f"//-----------------------------------------------------------\n"
            f"// Wrapper of placeholder {cfg.orig_comp_name}\n"
            f"//-----------------------------------------------------------\n"
            f"\n"
            f"`ifndef {cfg.wrapper_guard_symbol}\n"
            f"`define {cfg.wrapper_guard_symbol}\n"
            f"\n"
            f"{{pickled_wrapper}}\n"
            f"\n"
            f"`endif /* {cfg.wrapper_guard_symbol} */\n")

        pickled_wrapper_source = pickled_wrapper_source_tplt.format(
            pickled_wrapper=pickled_wrapper)
        pickled_source = pickled_dependency_source + pickled_wrapper_source

        cfg.pickled_wrapper_template = pickled_wrapper_source_tplt.format(
            pickled_wrapper=tplt)
        cfg.pickled_wrapper_nlines = len(
            pickled_wrapper_source.split('\n')) - 1

        with open(cfg.pickled_source_file, 'w') as fd:
            fd.write(pickled_source)
            fd.flush()
            os.fsync(fd)
            fd.close()

    def _get_v_lib_files(s, m, cfg, irepr):
        orig_comp_name = cfg.orig_comp_name
        tplt = dedent("""\
            // The source code below are included because they are specified
            // as the v_libs Verilog placeholder option of component {orig_comp_name}.

            // If you get a duplicated def error from files included below, please
            // make sure they are included either through the v_libs option or the
            // explicit `include statement in the Verilog source code -- if they
            // appear in both then they will be included twice!

            {v_libs}
            // End of all v_libs files for component {orig_comp_name}

        """)
        if cfg.v_libs:
            v_libs = s._import_sources(cfg, cfg.v_libs)
        else:
            v_libs = ""

        return tplt.format(**locals())

    def _get_dependent_verilog_modules(s, m, cfg, irepr):
        return s._import_sources(cfg, [cfg.src_file])

    def _gen_verilog_wrapper(s, m, cfg, irepr):
        # By default use single underscore as separator
        rtlir_ports = gen_mapped_ports(m, cfg.port_map, cfg.has_clk,
                                       cfg.has_reset, '_')

        all_port_names = list(map(lambda x: x[1], rtlir_ports))

        if not cfg.params:
            parameters = irepr.get_params()
        else:
            parameters = cfg.params.items()

        # Port definitions of wrapper
        ports = []
        for idx, (_, name, p, _) in enumerate(rtlir_ports):
            if name:
                if isinstance(p, rt.Array):
                    n_dim = p.get_dim_sizes()
                    s_dim = ''.join([f'[0:{idx-1}]' for idx in n_dim])
                    p = p.get_next_dim_type()
                else:
                    s_dim = ''
                ports.append(
                    f"  {p.get_direction()} logic [{p.get_dtype().get_length()}-1:0]"\
                    f" {name} {s_dim}{'' if idx == len(rtlir_ports)-1 else ','}"
                )

        # The wrapper has to have an unused clk port to make verilator
        # VCD tracing work.
        if 'clk' not in all_port_names:
            ports.insert(0, '  input logic clk,')

        if 'reset' not in all_port_names:
            ports.insert(0, '  input logic reset,')

        # Parameters passed to the module to be parametrized
        params = [
          f"    .{param}( {val} ){'' if idx == len(parameters)-1 else ','}"\
          for idx, (param, val) in enumerate(parameters)
        ]

        # Connections between top module and inner module
        connect_ports = [
          f"    .{name}( {name} ){'' if idx == len(rtlir_ports)-1 else ','}"\
          for idx, (_, name, p, _) in enumerate(rtlir_ports) if name
        ]

        lines = [
            f"module {cfg.pickled_top_module}",
            "(",
        ] + ports + [
            ");",
            f"  {cfg.top_module}",
            "  #(",
        ] + params + [
            "  ) v",
            "  (",
        ] + connect_ports + [
            "  );",
            "endmodule",
        ]

        template_lines = [
            "module {top_module_name}",
            "(",
        ] + ports + [
            ");",
            f"  {cfg.top_module}",
            "  #(",
        ] + params + [
            "  ) v",
            "  (",
        ] + connect_ports + [
            "  );",
            "endmodule",
        ]

        return '\n'.join(line for line in lines), '\n'.join(
            line for line in template_lines)

    #-----------------------------------------------------------------------
    # _has_pin
    #-----------------------------------------------------------------------

    def _has_pin(s, src_file, direction, nbits, pin):
        trim = lambda s: ''.join(s.split())
        targets = [
            f'{direction}logic[{nbits-1}:0]{pin}',
            f'{direction}[{nbits-1}:0]{pin}'
        ]
        if nbits == 1:
            targets.append(f'{direction}logic{pin}')
            targets.append(f'{direction}{pin}')
        try:
            with open(src_file) as fd:
                for line in fd.readlines():
                    if any(map(lambda x: x in trim(line), targets)):
                        return True
            return False
        except OSError:
            return False

    #-----------------------------------------------------------------------
    # _get_auto_prefix
    #-----------------------------------------------------------------------

    def _get_auto_prefix(s, m):
        parent_dir = cwd = os.path.dirname(inspect.getfile(m.__class__))
        while cwd != '/':
            if os.path.exists(f"{cwd}{os.path.sep}pymtl.ini"):
                break
            cwd = os.path.dirname(cwd)

        if cwd == '/': return ''

        pymtl_config = configparser.ConfigParser()
        pymtl_config.read(f"{cwd}{os.path.sep}pymtl.ini")

        if 'placeholder' in pymtl_config and \
           'auto_prefix' in pymtl_config['placeholder'] and \
           pymtl_config.getboolean( 'placeholder', 'auto_prefix' ):
            return f"{os.path.basename(parent_dir)}_"
        else:
            return ''

    #-----------------------------------------------------------------------
    # import_sources
    #-----------------------------------------------------------------------
    # The right way to do this is to use a recursive function like I have
    # done below. This ensures that files are inserted into the output stream
    # in the correct order. -cbatten

    # Regex to extract verilog filenames from `include statements

    _include_re = re.compile(r'"(?P<filename>[\w/\.-]*)"')

    def _output_verilog_file(s, include_path, verilog_file):
        code = ""
        with open(verilog_file) as fp:

            short_verilog_file = verilog_file
            if verilog_file.startswith(include_path + "/"):
                short_verilog_file = verilog_file[len(include_path + "/"):]

            code += '`line 1 "{}" 0\n'.format(short_verilog_file)

            line_num = 0
            for line in fp:
                line_num += 1
                if '`include' in line:
                    re_result = s._include_re.search(line)
                else:
                    re_result = None

                if re_result:
                    filename = re_result.group('filename')
                    fullname = os.path.join(include_path, filename)
                    code += s._output_verilog_file(include_path, fullname)
                    code += '\n'
                    code += '`line {} "{}" 0\n'.format(line_num + 1,
                                                       short_verilog_file)
                else:
                    code += line
        return code

    def _import_sources(s, cfg, source_list):
        # Import Verilog source from all Verilog files source_list. This includes
        # any source files specified by `include within those files.

        code = ""

        if not source_list:
            return

        # We will use the first verilog file to find the root of PyMTL project

        first_verilog_file = source_list[0]

        # All verilog includes are relative to the root of the PyMTL project.
        # We identify the root of the PyMTL project by looking for the special
        # pymtl.ini file.

        _path = os.path.dirname(first_verilog_file)
        special_file_found = False
        include_path = os.path.dirname(os.path.abspath(first_verilog_file))
        while include_path != "/":
            if os.path.exists(include_path + os.path.sep + "pymtl.ini"):
                special_file_found = True
                sys.path.insert(0, include_path)
                break
            include_path = os.path.dirname(include_path)

        # Append the user-defined include path to include_path
        # NOTE: the current pickler only supports one include path. If v_include
        # config is present, use it instead.

        if cfg.v_include:
            if len(cfg.v_include) != 1:
                raise InvalidPassOptionValue(
                    "v_include", cfg.v_include, cfg.Pass.__name__,
                    'the current pickler only supports one user-defined v_include path...'
                )
            include_path = cfg.v_include[0]

        # If we could not find the special .pymtl-python-path file, then assume
        # the include directory is the same as the directory that contains the
        # first verilog file.

        if not special_file_found and not cfg.v_include:
            include_path = os.path.dirname(os.path.abspath(first_verilog_file))

        # Regex to extract verilog filenames from `include statements

        s._include_re = re.compile(r'"(?P<filename>[\w/\.-]*)"')

        # Iterate through all source files and add any `include files to the
        # list of source files to import.

        for source in source_list:
            code += s._output_verilog_file(include_path, source)

        return code
Esempio n. 9
0
class RTLIRPass(BasePass):

    #: An RTLIR getter
    #:
    #: Type: ``RTLIRGetter``; output
    rtlir_getter = MetadataKey()