litex/gen: reintegrate migen with modifications to be able to simulate with vpi until...
authorFlorent Kermarrec <florent@enjoy-digital.fr>
Thu, 12 Nov 2015 19:06:59 +0000 (20:06 +0100)
committerFlorent Kermarrec <florent@enjoy-digital.fr>
Fri, 13 Nov 2015 13:44:16 +0000 (14:44 +0100)
39 files changed:
litex/gen/MIGEN_LICENSE [new file with mode: 0644]
litex/gen/__init__.py
litex/gen/fhdl/__init__.py [new file with mode: 0644]
litex/gen/fhdl/bitcontainer.py [new file with mode: 0644]
litex/gen/fhdl/conv_output.py [new file with mode: 0644]
litex/gen/fhdl/decorators.py [new file with mode: 0644]
litex/gen/fhdl/edif.py [new file with mode: 0644]
litex/gen/fhdl/module.py [new file with mode: 0644]
litex/gen/fhdl/namer.py [new file with mode: 0644]
litex/gen/fhdl/simplify.py [new file with mode: 0644]
litex/gen/fhdl/specials.py [new file with mode: 0644]
litex/gen/fhdl/structure.py [new file with mode: 0644]
litex/gen/fhdl/tools.py [new file with mode: 0644]
litex/gen/fhdl/tracer.py [new file with mode: 0644]
litex/gen/fhdl/verilog.py [new file with mode: 0644]
litex/gen/fhdl/visit.py [new file with mode: 0644]
litex/gen/genlib/__init__.py [new file with mode: 0644]
litex/gen/genlib/cdc.py [new file with mode: 0644]
litex/gen/genlib/coding.py [new file with mode: 0644]
litex/gen/genlib/divider.py [new file with mode: 0644]
litex/gen/genlib/fifo.py [new file with mode: 0644]
litex/gen/genlib/fsm.py [new file with mode: 0644]
litex/gen/genlib/io.py [new file with mode: 0644]
litex/gen/genlib/misc.py [new file with mode: 0644]
litex/gen/genlib/record.py [new file with mode: 0644]
litex/gen/genlib/resetsync.py [new file with mode: 0644]
litex/gen/genlib/roundrobin.py [new file with mode: 0644]
litex/gen/genlib/sort.py [new file with mode: 0644]
litex/gen/sim/__init__.py [new file with mode: 0644]
litex/gen/sim/generic.py [new file with mode: 0644]
litex/gen/sim/icarus.py [new file with mode: 0644]
litex/gen/sim/ipc.py [new file with mode: 0644]
litex/gen/sim/upper.py [new file with mode: 0644]
litex/gen/util/__init__.py [new file with mode: 0644]
litex/gen/util/misc.py [new file with mode: 0644]
litex/gen/vpi/Makefile [new file with mode: 0644]
litex/gen/vpi/ipc.c [new file with mode: 0644]
litex/gen/vpi/ipc.h [new file with mode: 0644]
litex/gen/vpi/main.c [new file with mode: 0644]

diff --git a/litex/gen/MIGEN_LICENSE b/litex/gen/MIGEN_LICENSE
new file mode 100644 (file)
index 0000000..4f29060
--- /dev/null
@@ -0,0 +1,31 @@
+Unless otherwise noted, Migen is copyright (C) 2011-2013 Sebastien Bourdeauducq.
+The simulation extension (as mentioned in the comments at the beginning of the
+corresponding source files) is copyright (C) 2012 Vermeer Manufacturing Co. All
+rights reserved.
+
+Redistribution and use in source and binary forms, with or without modification,
+are permitted provided that the following conditions are met:
+
+1. Redistributions of source code must retain the above copyright notice, this
+   list of conditions and the following disclaimer. 
+2. Redistributions in binary form must reproduce the above copyright notice,
+   this list of conditions and the following disclaimer in the documentation
+   and/or other materials provided with the distribution. 
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
+ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+
+Other authors retain ownership of their contributions. If a submission can
+reasonably be considered independently copyrightable, it's yours and we
+encourage you to claim it with appropriate copyright notices. This submission
+then falls under the "otherwise noted" category. All submissions are strongly
+encouraged to use the two-clause BSD license reproduced above.
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..3f70cb1f64b943a96a7546c8082c7b89173087cc 100644 (file)
@@ -0,0 +1,8 @@
+from litex.gen.fhdl.structure import *
+from litex.gen.fhdl.module import *
+from litex.gen.fhdl.specials import *
+from litex.gen.fhdl.bitcontainer import *
+from litex.gen.fhdl.decorators import *
+
+from litex.gen.genlib.record import *
+from litex.gen.genlib.fsm import *
diff --git a/litex/gen/fhdl/__init__.py b/litex/gen/fhdl/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/litex/gen/fhdl/bitcontainer.py b/litex/gen/fhdl/bitcontainer.py
new file mode 100644 (file)
index 0000000..11a3ede
--- /dev/null
@@ -0,0 +1,121 @@
+from litex.gen.fhdl import structure as f
+
+
+__all__ = ["log2_int", "bits_for", "value_bits_sign"]
+
+
+def log2_int(n, need_pow2=True):
+    l = 1
+    r = 0
+    while l < n:
+        l *= 2
+        r += 1
+    if need_pow2 and l != n:
+        raise ValueError("Not a power of 2")
+    return r
+
+
+def bits_for(n, require_sign_bit=False):
+    if n > 0:
+        r = log2_int(n + 1, False)
+    else:
+        require_sign_bit = True
+        r = log2_int(-n, False)
+    if require_sign_bit:
+        r += 1
+    return r
+
+
+def value_bits_sign(v):
+    """Bit length and signedness of a value.
+
+    Parameters
+    ----------
+    v : Value
+
+    Returns
+    -------
+    int, bool
+        Number of bits required to store `v` or available in `v`, followed by
+        whether `v` has a sign bit (included in the bit count).
+
+    Examples
+    --------
+    >>> value_bits_sign(f.Signal(8))
+    8, False
+    >>> value_bits_sign(C(0xaa))
+    8, False
+    """
+    if isinstance(v, (f.Constant, f.Signal)):
+        return v.nbits, v.signed
+    elif isinstance(v, (f.ClockSignal, f.ResetSignal)):
+        return 1, False
+    elif isinstance(v, f._Operator):
+        obs = list(map(value_bits_sign, v.operands))
+        if v.op == "+" or v.op == "-":
+            if not obs[0][1] and not obs[1][1]:
+                # both operands unsigned
+                return max(obs[0][0], obs[1][0]) + 1, False
+            elif obs[0][1] and obs[1][1]:
+                # both operands signed
+                return max(obs[0][0], obs[1][0]) + 1, True
+            elif not obs[0][1] and obs[1][1]:
+                # first operand unsigned (add sign bit), second operand signed
+                return max(obs[0][0] + 1, obs[1][0]) + 1, True
+            else:
+                # first signed, second operand unsigned (add sign bit)
+                return max(obs[0][0], obs[1][0] + 1) + 1, True
+        elif v.op == "*":
+            if not obs[0][1] and not obs[1][1]:
+                # both operands unsigned
+                return obs[0][0] + obs[1][0], False
+            elif obs[0][1] and obs[1][1]:
+                # both operands signed
+                return obs[0][0] + obs[1][0] - 1, True
+            else:
+                # one operand signed, the other unsigned (add sign bit)
+                return obs[0][0] + obs[1][0] + 1 - 1, True
+        elif v.op == "<<<":
+            if obs[1][1]:
+                extra = 2**(obs[1][0] - 1) - 1
+            else:
+                extra = 2**obs[1][0] - 1
+            return obs[0][0] + extra, obs[0][1]
+        elif v.op == ">>>":
+            if obs[1][1]:
+                extra = 2**(obs[1][0] - 1)
+            else:
+                extra = 0
+            return obs[0][0] + extra, obs[0][1]
+        elif v.op == "&" or v.op == "^" or v.op == "|":
+            if not obs[0][1] and not obs[1][1]:
+                # both operands unsigned
+                return max(obs[0][0], obs[1][0]), False
+            elif obs[0][1] and obs[1][1]:
+                # both operands signed
+                return max(obs[0][0], obs[1][0]), True
+            elif not obs[0][1] and obs[1][1]:
+                # first operand unsigned (add sign bit), second operand signed
+                return max(obs[0][0] + 1, obs[1][0]), True
+            else:
+                # first signed, second operand unsigned (add sign bit)
+                return max(obs[0][0], obs[1][0] + 1), True
+        elif v.op == "<" or v.op == "<=" or v.op == "==" or v.op == "!=" \
+          or v.op == ">" or v.op == ">=":
+              return 1, False
+        elif v.op == "~":
+            return obs[0]
+        else:
+            raise TypeError
+    elif isinstance(v, f._Slice):
+        return v.stop - v.start, value_bits_sign(v.value)[1]
+    elif isinstance(v, f.Cat):
+        return sum(value_bits_sign(sv)[0] for sv in v.l), False
+    elif isinstance(v, f.Replicate):
+        return (value_bits_sign(v.v)[0])*v.n, False
+    elif isinstance(v, f._ArrayProxy):
+        bsc = list(map(value_bits_sign, v.choices))
+        return max(bs[0] for bs in bsc), any(bs[1] for bs in bsc)
+    else:
+        raise TypeError("Can not calculate bit length of {} {}".format(
+            type(v), v))
diff --git a/litex/gen/fhdl/conv_output.py b/litex/gen/fhdl/conv_output.py
new file mode 100644 (file)
index 0000000..793fad2
--- /dev/null
@@ -0,0 +1,35 @@
+from operator import itemgetter
+
+
+class ConvOutput:
+    def __init__(self):
+        self.main_source = ""
+        self.data_files = dict()
+
+    def set_main_source(self, src):
+        self.main_source = src
+
+    def add_data_file(self, filename_base, content):
+        filename = filename_base
+        i = 1
+        while filename in self.data_files:
+            parts = filename_base.split(".", maxsplit=1)
+            parts[0] += "_" + str(i)
+            filename = ".".join(parts)
+            i += 1
+        self.data_files[filename] = content
+        return filename
+
+    def __str__(self):
+        r = self.main_source + "\n"
+        for filename, content in sorted(self.data_files.items(),
+                                        key=itemgetter(0)):
+            r += filename + ":\n" + content
+        return r
+
+    def write(self, main_filename):
+        with open(main_filename, "w") as f:
+            f.write(self.main_source)
+        for filename, content in self.data_files.items():
+            with open(filename, "w") as f:
+                f.write(content)
diff --git a/litex/gen/fhdl/decorators.py b/litex/gen/fhdl/decorators.py
new file mode 100644 (file)
index 0000000..59444ea
--- /dev/null
@@ -0,0 +1,107 @@
+from litex.gen.fhdl.structure import *
+from litex.gen.fhdl.module import Module
+from litex.gen.fhdl.tools import insert_reset, rename_clock_domain
+
+
+__all__ = ["CEInserter", "ResetInserter", "ClockDomainsRenamer",
+           "ModuleTransformer"]
+
+
+class ModuleTransformer:
+    # overload this in derived classes
+    def transform_instance(self, i):
+        pass
+
+    # overload this in derived classes
+    def transform_fragment(self, i, f):
+        pass
+
+    def wrap_class(self, victim):
+        class Wrapped(victim):
+            def __init__(i, *args, **kwargs):
+                victim.__init__(i, *args, **kwargs)
+                self.transform_instance(i)
+
+            def get_fragment(i):
+                f = victim.get_fragment(i)
+                self.transform_fragment(i, f)
+                return f
+
+        Wrapped.__name__ = victim.__name__
+        # "{}_{}".format(self.__class__.__name__, victim.__name__)
+        return Wrapped
+
+    def wrap_instance(self, victim):
+        self.transform_instance(victim)
+        orig_get_fragment = victim.get_fragment
+
+        def get_fragment():
+            f = orig_get_fragment()
+            self.transform_fragment(victim, f)
+            return f
+
+        victim.get_fragment = get_fragment
+        return victim
+
+    def __call__(self, victim):
+        if isinstance(victim, Module):
+            return self.wrap_instance(victim)
+        else:
+            return self.wrap_class(victim)
+
+
+class ControlInserter(ModuleTransformer):
+    control_name = None  # override this
+
+    def __init__(self, clock_domains=None):
+        self.clock_domains = clock_domains
+
+    def transform_instance(self, i):
+        if self.clock_domains is None:
+            ctl = Signal(name=self.control_name)
+            assert not hasattr(i, self.control_name)
+            setattr(i, self.control_name, ctl)
+        else:
+            for cd in self.clock_domains:
+                name = self.control_name + "_" + cd
+                ctl = Signal(name=name)
+                assert not hasattr(i, name)
+                setattr(i, name, ctl)
+
+    def transform_fragment(self, i, f):
+        if self.clock_domains is None:
+            if len(f.sync) != 1:
+                raise ValueError("Control signal clock domains must be specified when module has more than one domain")
+            cdn = list(f.sync.keys())[0]
+            to_insert = [(getattr(i, self.control_name), cdn)]
+        else:
+            to_insert = [(getattr(i, self.control_name + "_" + cdn), cdn)
+                for cdn in self.clock_domains]
+        self.transform_fragment_insert(i, f, to_insert)
+
+
+class CEInserter(ControlInserter):
+    control_name = "ce"
+
+    def transform_fragment_insert(self, i, f, to_insert):
+        for ce, cdn in to_insert:
+            f.sync[cdn] = [If(ce, *f.sync[cdn])]
+
+
+class ResetInserter(ControlInserter):
+    control_name = "reset"
+
+    def transform_fragment_insert(self, i, f, to_insert):
+        for reset, cdn in to_insert:
+            f.sync[cdn] = insert_reset(reset, f.sync[cdn])
+
+
+class ClockDomainsRenamer(ModuleTransformer):
+    def __init__(self, cd_remapping):
+        if isinstance(cd_remapping, str):
+            cd_remapping = {"sys": cd_remapping}
+        self.cd_remapping = cd_remapping
+
+    def transform_fragment(self, i, f):
+        for old, new in self.cd_remapping.items():
+            rename_clock_domain(f, old, new)
diff --git a/litex/gen/fhdl/edif.py b/litex/gen/fhdl/edif.py
new file mode 100644 (file)
index 0000000..f68c99b
--- /dev/null
@@ -0,0 +1,213 @@
+from collections import OrderedDict, namedtuple
+
+from litex.gen.fhdl.structure import *
+from litex.gen.fhdl.namer import build_namespace
+from litex.gen.fhdl.tools import list_special_ios
+from litex.gen.fhdl.structure import _Fragment
+from litex.gen.fhdl.conv_output import ConvOutput
+
+
+_Port = namedtuple("_Port", "name direction")
+_Cell = namedtuple("_Cell", "name ports")
+_Property = namedtuple("_Property", "name value")
+_Instance = namedtuple("_Instance", "name cell properties")
+_NetBranch = namedtuple("_NetBranch", "portname instancename")
+
+
+def _write_cells(cells):
+    r = ""
+    for cell in cells:
+        r += """
+        (cell {0.name}
+            (cellType GENERIC)
+                (view view_1
+                    (viewType NETLIST)
+                    (interface""".format(cell)
+        for port in cell.ports:
+            r += """
+                        (port {0.name} (direction {0.direction}))""".format(port)
+        r += """
+                    )
+                )
+        )"""
+    return r
+
+
+def _write_io(ios):
+    r = ""
+    for s in ios:
+        r += """
+                        (port {0.name} (direction {0.direction}))""".format(s)
+    return r
+
+
+def _write_instantiations(instances, cell_library):
+    instantiations = ""
+    for instance in instances:
+        instantiations += """
+                        (instance {0.name}
+                            (viewRef view_1 (cellRef {0.cell} (libraryRef {1})))""".format(instance, cell_library)
+        for prop in instance.properties:
+            instantiations += """
+                            (property {0} (string "{1}"))""".format(prop.name, prop.value)
+        instantiations += """
+                        )"""
+    return instantiations
+
+
+def _write_connections(connections):
+    r = ""
+    for netname, branches in connections.items():
+        r += """
+                        (net {0}
+                            (joined""".format(netname)
+        for branch in branches:
+            r += """
+                                (portRef {0}{1})""".format(branch.portname, "" if branch.instancename == "" else " (instanceRef {})".format(branch.instancename))
+        r += """
+                            )
+                        )"""
+    return r
+
+
+def _write_edif(cells, ios, instances, connections, cell_library, design_name, part, vendor):
+    r = """(edif {0}
+    (edifVersion 2 0 0)
+    (edifLevel 0)
+    (keywordMap (keywordLevel 0))
+    (external {1}
+        (edifLevel 0)
+        (technology (numberDefinition))""".format(design_name, cell_library)
+    r += _write_cells(cells)
+    r += """
+    )
+    (library {0}_lib
+        (edifLevel 0)
+        (technology (numberDefinition))
+        (cell {0}
+            (cellType GENERIC)
+                (view view_1
+                    (viewType NETLIST)
+                    (interface""".format(design_name)
+    r += _write_io(ios)
+    r += """
+                        (designator "{0}")
+                    )
+                    (contents""".format(part)
+    r += _write_instantiations(instances, cell_library)
+    r += _write_connections(connections)
+    r += """
+                    )
+                )
+        )
+    )
+    (design {0}
+        (cellRef {0} (libraryRef {0}_lib))
+        (property PART (string "{1}") (owner "{2}"))
+    )
+)""".format(design_name, part, vendor)
+
+    return r
+
+
+def _generate_cells(f):
+    cell_dict = OrderedDict()
+    for special in f.specials:
+        if isinstance(special, Instance):
+            port_list = []
+            for port in special.items:
+                if isinstance(port, Instance.Input):
+                    port_list.append(_Port(port.name, "INPUT"))
+                elif isinstance(port, Instance.Output):
+                    port_list.append(_Port(port.name, "OUTPUT"))
+                elif isinstance(port, Instance.InOut):
+                    port_list.append(_Port(port.name, "INOUT"))
+                elif isinstance(port, Instance.Parameter):
+                    pass
+                else:
+                    raise NotImplementedError("Unsupported instance item")
+            if special.of in cell_dict:
+                if set(port_list) != set(cell_dict[special.of]):
+                    raise ValueError("All instances must have the same ports for EDIF conversion")
+            else:
+                cell_dict[special.of] = port_list
+        else:
+            raise ValueError("EDIF conversion can only handle synthesized fragments")
+    return [_Cell(k, v) for k, v in cell_dict.items()]
+
+
+def _generate_instances(f, ns):
+    instances = []
+    for special in f.specials:
+        if isinstance(special, Instance):
+            props = []
+            for prop in special.items:
+                if isinstance(prop, Instance.Input):
+                    pass
+                elif isinstance(prop, Instance.Output):
+                    pass
+                elif isinstance(prop, Instance.InOut):
+                    pass
+                elif isinstance(prop, Instance.Parameter):
+                    props.append(_Property(name=prop.name, value=prop.value))
+                else:
+                    raise NotImplementedError("Unsupported instance item")
+            instances.append(_Instance(name=ns.get_name(special), cell=special.of, properties=props))
+        else:
+            raise ValueError("EDIF conversion can only handle synthesized fragments")
+    return instances
+
+
+def _generate_ios(f, ios, ns):
+    outs = list_special_ios(f, False, True, False)
+    inouts = list_special_ios(f, False, False, True)
+    r = []
+    for io in ios:
+        direction = "OUTPUT" if io in outs else "INOUT" if io in inouts else "INPUT"
+        r.append(_Port(name=ns.get_name(io), direction=direction))
+    return r
+
+
+def _generate_connections(f, ios, ns):
+    r = OrderedDict()
+    for special in f.specials:
+        if isinstance(special, Instance):
+            instname = ns.get_name(special)
+            for port in special.items:
+                if isinstance(port, Instance._IO):
+                    s = ns.get_name(port.expr)
+                    if s not in r:
+                        r[s] = []
+                    r[s].append(_NetBranch(portname=port.name, instancename=instname))
+                elif isinstance(port, Instance.Parameter):
+                    pass
+                else:
+                    raise NotImplementedError("Unsupported instance item")
+        else:
+            raise ValueError("EDIF conversion can only handle synthesized fragments")
+    for s in ios:
+        io = ns.get_name(s)
+        if io not in r:
+            r[io] = []
+        r[io].append(_NetBranch(portname=io, instancename=""))
+    return r
+
+
+def convert(f, ios, cell_library, vendor, device, name="top"):
+    if not isinstance(f, _Fragment):
+        f = f.get_fragment()
+    if f.comb != [] or f.sync != {}:
+        raise ValueError("EDIF conversion can only handle synthesized fragments")
+    if ios is None:
+        ios = set()
+    cells = _generate_cells(f)
+    ns = build_namespace(list_special_ios(f, True, True, True))
+    instances = _generate_instances(f, ns)
+    inouts = _generate_ios(f, ios, ns)
+    connections = _generate_connections(f, ios, ns)
+    src = _write_edif(cells, inouts, instances, connections, cell_library, name, device, vendor)
+
+    r = ConvOutput()
+    r.set_main_source(src)
+    r.ns = ns
+    return r
diff --git a/litex/gen/fhdl/module.py b/litex/gen/fhdl/module.py
new file mode 100644 (file)
index 0000000..73848bb
--- /dev/null
@@ -0,0 +1,198 @@
+import collections
+from itertools import combinations
+
+from litex.gen.util.misc import flat_iteration
+from litex.gen.fhdl.structure import *
+from litex.gen.fhdl.structure import _Fragment
+from litex.gen.fhdl.tools import rename_clock_domain
+from litex.gen.sim.upper import gen_sim, proxy_sim
+
+__all__ = ["Module", "FinalizeError"]
+
+
+class FinalizeError(Exception):
+    pass
+
+
+def _flat_list(e):
+    if isinstance(e, collections.Iterable):
+        return flat_iteration(e)
+    else:
+        return [e]
+
+
+class _ModuleProxy:
+    def __init__(self, fm):
+        object.__setattr__(self, "_fm", fm)
+
+
+class _ModuleComb(_ModuleProxy):
+    def __iadd__(self, other):
+        self._fm._fragment.comb += _flat_list(other)
+        return self
+
+
+def _cd_append(d, key, statements):
+    try:
+        l = d[key]
+    except KeyError:
+        l = []
+        d[key] = l
+    l += _flat_list(statements)
+
+
+class _ModuleSyncCD:
+    def __init__(self, fm, cd):
+        self._fm = fm
+        self._cd = cd
+
+    def __iadd__(self, other):
+        _cd_append(self._fm._fragment.sync, self._cd, other)
+        return self
+
+
+class _ModuleSync(_ModuleProxy):
+    def __iadd__(self, other):
+        _cd_append(self._fm._fragment.sync, "sys", other)
+        return self
+
+    def __getattr__(self, name):
+        return _ModuleSyncCD(self._fm, name)
+
+    def __setattr__(self, name, value):
+        if not isinstance(value, _ModuleSyncCD):
+            raise AttributeError("Attempted to assign sync property - use += instead")
+
+
+# _ModuleForwardAttr enables user classes to do e.g.:
+# self.subm.foobar = SomeModule()
+# and then access the submodule with self.foobar.
+class _ModuleForwardAttr:
+    def __setattr__(self, name, value):
+        self.__iadd__(value)
+        setattr(self._fm, name, value)
+
+
+class _ModuleSpecials(_ModuleProxy, _ModuleForwardAttr):
+    def __iadd__(self, other):
+        self._fm._fragment.specials |= set(_flat_list(other))
+        return self
+
+
+class _ModuleSubmodules(_ModuleProxy):
+    def __setattr__(self, name, value):
+        self._fm._submodules += [(name, e) for e in _flat_list(value)]
+        setattr(self._fm, name, value)
+
+    def __iadd__(self, other):
+        self._fm._submodules += [(None, e) for e in _flat_list(other)]
+        return self
+
+
+class _ModuleClockDomains(_ModuleProxy, _ModuleForwardAttr):
+    def __iadd__(self, other):
+        self._fm._fragment.clock_domains += _flat_list(other)
+        return self
+
+
+class Module:
+    def get_fragment(self):
+        assert(not self.get_fragment_called)
+        self.get_fragment_called = True
+        self.finalize()
+        return self._fragment
+
+    def __getattr__(self, name):
+        if name == "comb":
+            return _ModuleComb(self)
+        elif name == "sync":
+            return _ModuleSync(self)
+        elif name == "specials":
+            return _ModuleSpecials(self)
+        elif name == "submodules":
+            return _ModuleSubmodules(self)
+        elif name == "clock_domains":
+            return _ModuleClockDomains(self)
+
+        # hack to have initialized regular attributes without using __init__
+        # (which would require derived classes to call it)
+        elif name == "finalized":
+            self.finalized = False
+            return self.finalized
+        elif name == "_fragment":
+            simf = None
+            try:
+                simf = self.do_simulation
+            except AttributeError:
+                try:
+                    simg = self.gen_simulation
+                except AttributeError:
+                    pass
+                else:
+                    simf = gen_sim(simg)
+            if simf is not None:
+                simf = proxy_sim(self, simf)
+            sim = [] if simf is None else [simf]
+            self._fragment = _Fragment(sim=sim)
+            return self._fragment
+        elif name == "_submodules":
+            self._submodules = []
+            return self._submodules
+        elif name == "_clock_domains":
+            self._clock_domains = []
+            return self._clock_domains
+        elif name == "get_fragment_called":
+            self.get_fragment_called = False
+            return self.get_fragment_called
+
+        else:
+            raise AttributeError("'"+self.__class__.__name__+"' object has no attribute '"+name+"'")
+
+    def __setattr__(self, name, value):
+        if name in ["comb", "sync", "specials", "submodules", "clock_domains"]:
+            if not isinstance(value, _ModuleProxy):
+                raise AttributeError("Attempted to assign special Module property - use += instead")
+        else:
+            object.__setattr__(self, name, value)
+
+    def _collect_submodules(self):
+        r = []
+        for name, submodule in self._submodules:
+            if not submodule.get_fragment_called:
+                r.append((name, submodule.get_fragment()))
+        return r
+
+    def finalize(self, *args, **kwargs):
+        if not self.finalized:
+            self.finalized = True
+            # finalize existing submodules before finalizing us
+            subfragments = self._collect_submodules()
+            self.do_finalize(*args, **kwargs)
+            # finalize submodules created by do_finalize
+            subfragments += self._collect_submodules()
+            # resolve clock domain name conflicts
+            needs_renaming = set()
+            for (mod_name1, f1), (mod_name2, f2) in combinations(subfragments, 2):
+                f1_names = set(cd.name for cd in f1.clock_domains)
+                f2_names = set(cd.name for cd in f2.clock_domains)
+                common_names = f1_names & f2_names
+                if common_names:
+                    if mod_name1 is None or mod_name2 is None:
+                        raise ValueError("Multiple submodules with local clock domains cannot be anonymous")
+                    if mod_name1 == mod_name2:
+                        raise ValueError("Multiple submodules with local clock domains cannot have the same name")
+                needs_renaming |= common_names
+            for mod_name, f in subfragments:
+                for cd in f.clock_domains:
+                    if cd.name in needs_renaming:
+                        rename_clock_domain(f, cd.name, mod_name + "_" + cd.name)
+            # sum subfragments
+            for mod_name, f in subfragments:
+                self._fragment += f
+
+    def do_finalize(self):
+        pass
+
+    def do_exit(self, *args, **kwargs):
+        for name, submodule in self._submodules:
+            submodule.do_exit(*args, **kwargs)
diff --git a/litex/gen/fhdl/namer.py b/litex/gen/fhdl/namer.py
new file mode 100644 (file)
index 0000000..5e8ecdd
--- /dev/null
@@ -0,0 +1,258 @@
+from collections import OrderedDict
+from itertools import combinations
+
+from litex.gen.fhdl.structure import *
+
+
+class _Node:
+    def __init__(self):
+        self.signal_count = 0
+        self.numbers = set()
+        self.use_name = False
+        self.use_number = False
+        self.children = OrderedDict()
+
+
+def _display_tree(filename, tree):
+    from litex.gen.util.treeviz import RenderNode
+
+    def _to_render_node(name, node):
+        children = [_to_render_node(k, v) for k, v in node.children.items()]
+        if node.use_name:
+            if node.use_number:
+                color = (0.5, 0.9, 0.8)
+            else:
+                color = (0.8, 0.5, 0.9)
+        else:
+            if node.use_number:
+                color = (0.9, 0.8, 0.5)
+            else:
+                color = (0.8, 0.8, 0.8)
+        label = "{0}\n{1} signals\n{2}".format(name, node.signal_count, node.numbers)
+        return RenderNode(label, children, color=color)
+
+    top = _to_render_node("top", tree)
+    top.to_svg(filename)
+
+
+def _build_tree(signals, basic_tree=None):
+    root = _Node()
+    for signal in signals:
+        current_b = basic_tree
+        current = root
+        current.signal_count += 1
+        for name, number in signal.backtrace:
+            if basic_tree is None:
+                use_number = False
+            else:
+                current_b = current_b.children[name]
+                use_number = current_b.use_number
+            if use_number:
+                key = (name, number)
+            else:
+                key = name
+            try:
+                current = current.children[key]
+            except KeyError:
+                new = _Node()
+                current.children[key] = new
+                current = new
+            current.numbers.add(number)
+            if use_number:
+                current.all_numbers = sorted(current_b.numbers)
+            current.signal_count += 1
+    return root
+
+
+def _set_use_name(node, node_name=""):
+    cnames = [(k, _set_use_name(v, k)) for k, v in node.children.items()]
+    for (c1_prefix, c1_names), (c2_prefix, c2_names) in combinations(cnames, 2):
+        if not c1_names.isdisjoint(c2_names):
+            node.children[c1_prefix].use_name = True
+            node.children[c2_prefix].use_name = True
+    r = set()
+    for c_prefix, c_names in cnames:
+        if node.children[c_prefix].use_name:
+            for c_name in c_names:
+                r.add((c_prefix, ) + c_name)
+        else:
+            r |= c_names
+
+    if node.signal_count > sum(c.signal_count for c in node.children.values()):
+        node.use_name = True
+        r.add((node_name, ))
+
+    return r
+
+
+def _name_signal(tree, signal):
+    elements = []
+    treepos = tree
+    for step_name, step_n in signal.backtrace:
+        try:
+            treepos = treepos.children[(step_name, step_n)]
+            use_number = True
+        except KeyError:
+            treepos = treepos.children[step_name]
+            use_number = False
+        if treepos.use_name:
+            elname = step_name
+            if use_number:
+                elname += str(treepos.all_numbers.index(step_n))
+            elements.append(elname)
+    return "_".join(elements)
+
+
+def _build_pnd_from_tree(tree, signals):
+    return dict((signal, _name_signal(tree, signal)) for signal in signals)
+
+
+def _invert_pnd(pnd):
+    inv_pnd = dict()
+    for k, v in pnd.items():
+        inv_pnd[v] = inv_pnd.get(v, [])
+        inv_pnd[v].append(k)
+    return inv_pnd
+
+
+def _list_conflicting_signals(pnd):
+    inv_pnd = _invert_pnd(pnd)
+    r = set()
+    for k, v in inv_pnd.items():
+        if len(v) > 1:
+            r.update(v)
+    return r
+
+
+def _set_use_number(tree, signals):
+    for signal in signals:
+        current = tree
+        for step_name, step_n in signal.backtrace:
+            current = current.children[step_name]
+            current.use_number = current.signal_count > len(current.numbers) and len(current.numbers) > 1
+
+_debug = False
+
+
+def _build_pnd_for_group(group_n, signals):
+    basic_tree = _build_tree(signals)
+    _set_use_name(basic_tree)
+    if _debug:
+        _display_tree("tree{0}_basic.svg".format(group_n), basic_tree)
+    pnd = _build_pnd_from_tree(basic_tree, signals)
+
+    # If there are conflicts, try splitting the tree by numbers
+    # on paths taken by conflicting signals.
+    conflicting_signals = _list_conflicting_signals(pnd)
+    if conflicting_signals:
+        _set_use_number(basic_tree, conflicting_signals)
+        if _debug:
+            print("namer: using split-by-number strategy (group {0})".format(group_n))
+            _display_tree("tree{0}_marked.svg".format(group_n), basic_tree)
+        numbered_tree = _build_tree(signals, basic_tree)
+        _set_use_name(numbered_tree)
+        if _debug:
+            _display_tree("tree{0}_numbered.svg".format(group_n), numbered_tree)
+        pnd = _build_pnd_from_tree(numbered_tree, signals)
+    else:
+        if _debug:
+            print("namer: using basic strategy (group {0})".format(group_n))
+
+    # ...then add number suffixes by DUID
+    inv_pnd = _invert_pnd(pnd)
+    duid_suffixed = False
+    for name, signals in inv_pnd.items():
+        if len(signals) > 1:
+            duid_suffixed = True
+            for n, signal in enumerate(sorted(signals, key=lambda x: x.duid)):
+                pnd[signal] += str(n)
+    if _debug and duid_suffixed:
+        print("namer: using DUID suffixes (group {0})".format(group_n))
+
+    return pnd
+
+
+def _build_signal_groups(signals):
+    r = []
+    for signal in signals:
+        # build chain of related signals
+        related_list = []
+        cur_signal = signal
+        while cur_signal is not None:
+            related_list.insert(0, cur_signal)
+            cur_signal = cur_signal.related
+        # add to groups
+        for _ in range(len(related_list) - len(r)):
+            r.append(set())
+        for target_set, source_signal in zip(r, related_list):
+            target_set.add(source_signal)
+    # with the algorithm above and a list of all signals,
+    # a signal appears in all groups of a lower number than its.
+    # make signals appear only in their group of highest number.
+    for s1, s2 in zip(r, r[1:]):
+        s1 -= s2
+    return r
+
+
+def _build_pnd(signals):
+    groups = _build_signal_groups(signals)
+    gpnds = [_build_pnd_for_group(n, gsignals) for n, gsignals in enumerate(groups)]
+
+    pnd = dict()
+    for gn, gpnd in enumerate(gpnds):
+        for signal, name in gpnd.items():
+            result = name
+            cur_gn = gn
+            cur_signal = signal
+            while cur_signal.related is not None:
+                cur_signal = cur_signal.related
+                cur_gn -= 1
+                result = gpnds[cur_gn][cur_signal] + "_" + result
+            pnd[signal] = result
+
+    return pnd
+
+
+def build_namespace(signals, reserved_keywords=set()):
+    pnd = _build_pnd(signals)
+    ns = Namespace(pnd, reserved_keywords)
+    # register signals with name_override
+    for signal in signals:
+        if signal.name_override is not None:
+            ns.get_name(signal)
+    return ns
+
+
+class Namespace:
+    def __init__(self, pnd, reserved_keywords=set()):
+        self.counts = {k: 1 for k in reserved_keywords}
+        self.sigs = {}
+        self.pnd = pnd
+        self.clock_domains = dict()
+
+    def get_name(self, sig):
+        if isinstance(sig, ClockSignal):
+            sig = self.clock_domains[sig.cd].clk
+        if isinstance(sig, ResetSignal):
+            sig = self.clock_domains[sig.cd].rst
+            if sig is None:
+                raise ValueError("Attempted to obtain name of non-existent "
+                                 "reset signal of domain "+sig.cd)
+
+        if sig.name_override is not None:
+            sig_name = sig.name_override
+        else:
+            sig_name = self.pnd[sig]
+        try:
+            n = self.sigs[sig]
+        except KeyError:
+            try:
+                n = self.counts[sig_name]
+            except KeyError:
+                n = 0
+            self.sigs[sig] = n
+            self.counts[sig_name] = n + 1
+        if n:
+            return sig_name + "_" + str(n)
+        else:
+            return sig_name
diff --git a/litex/gen/fhdl/simplify.py b/litex/gen/fhdl/simplify.py
new file mode 100644 (file)
index 0000000..747f562
--- /dev/null
@@ -0,0 +1,114 @@
+from litex.gen.fhdl.structure import *
+from litex.gen.fhdl.specials import Memory, _MemoryPort, WRITE_FIRST, NO_CHANGE
+from litex.gen.fhdl.decorators import ModuleTransformer
+from litex.gen.util.misc import gcd_multiple
+
+
+class FullMemoryWE(ModuleTransformer):
+    def __init__(self):
+        self.replacments = dict()
+
+    def transform_fragment(self, i, f):
+        newspecials = set()
+
+        for orig in f.specials:
+            if not isinstance(orig, Memory):
+                newspecials.add(orig)
+                continue
+            global_granularity = gcd_multiple([p.we_granularity if p.we_granularity else orig.width for p in orig.ports])
+            if global_granularity == orig.width:
+                newspecials.add(orig)  # nothing to do
+            else:
+                newmems = []
+                for i in range(orig.width//global_granularity):
+                    if orig.init is None:
+                        newinit = None
+                    else:
+                        newinit = [(v >> i*global_granularity) & (2**global_granularity - 1) for v in orig.init]
+                    newmem = Memory(global_granularity, orig.depth, newinit, orig.name_override + "_grain" + str(i))
+                    newspecials.add(newmem)
+                    newmems.append(newmem)
+                    for port in orig.ports:
+                        port_granularity = port.we_granularity if port.we_granularity else orig.width
+                        newport = _MemoryPort(
+                            adr=port.adr,
+
+                            dat_r=port.dat_r[i*global_granularity:(i+1)*global_granularity] if port.dat_r is not None else None,
+                            we=port.we[i*global_granularity//port_granularity] if port.we is not None else None,
+                            dat_w=port.dat_w[i*global_granularity:(i+1)*global_granularity] if port.dat_w is not None else None,
+
+                            async_read=port.async_read,
+                            re=port.re,
+                            we_granularity=0,
+                            mode=port.mode,
+                            clock_domain=port.clock.cd)
+                        newmem.ports.append(newport)
+                        newspecials.add(newport)
+                self.replacments[orig] = newmems
+
+        f.specials = newspecials
+
+
+class MemoryToArray(ModuleTransformer):
+    def __init__(self):
+        self.replacements = dict()
+
+    def transform_fragment(self, i, f):
+        newspecials = set()
+
+        for mem in f.specials:
+            if not isinstance(mem, Memory):
+                newspecials.add(mem)
+                continue
+
+            storage = Array()
+            self.replacements[mem] = storage
+            init = []
+            if mem.init is not None:
+                init = mem.init
+            for d in init:
+                mem_storage = Signal(mem.width, reset=d)
+                storage.append(mem_storage)
+            for _ in range(mem.depth-len(init)):
+                mem_storage = Signal(mem.width)
+                storage.append(mem_storage)
+
+            for port in mem.ports:
+                if port.we_granularity:
+                    raise NotImplementedError
+                try:
+                    sync = f.sync[port.clock.cd]
+                except KeyError:
+                    sync = f.sync[port.clock.cd] = []
+
+                # read
+                if port.async_read:
+                    f.comb.append(port.dat_r.eq(storage[port.adr]))
+                else:
+                    if port.mode == WRITE_FIRST and port.we is not None:
+                        adr_reg = Signal.like(port.adr)
+                        rd_stmt = adr_reg.eq(port.adr)
+                        f.comb.append(port.dat_r.eq(storage[adr_reg]))
+                    elif port.mode == NO_CHANGE and port.we is not None:
+                        rd_stmt = If(~port.we, port.dat_r.eq(storage[port.adr]))
+                    else: # READ_FIRST or port.we is None, simplest case
+                        rd_stmt = port.dat_r.eq(storage[port.adr])
+                    if port.re is None:
+                        sync.append(rd_stmt)
+                    else:
+                        sync.append(If(port.re, rd_stmt))
+
+                # write
+                if port.we is not None:
+                    if port.we_granularity:
+                        n = mem.width//port.we_granularity
+                        for i in range(n):
+                            m = i*port.we_granularity
+                            M = (i+1)*port.we_granularity
+                            sync.append(If(port.we[i],
+                                        storage[port.adr][m:M].eq(port.dat_w)))
+                    else:
+                        sync.append(If(port.we,
+                                       storage[port.adr].eq(port.dat_w)))
+
+        f.specials = newspecials
diff --git a/litex/gen/fhdl/specials.py b/litex/gen/fhdl/specials.py
new file mode 100644 (file)
index 0000000..d4fc2bf
--- /dev/null
@@ -0,0 +1,360 @@
+from operator import itemgetter
+
+from litex.gen.fhdl.structure import *
+from litex.gen.fhdl.structure import _Value
+from litex.gen.fhdl.bitcontainer import bits_for, value_bits_sign
+from litex.gen.fhdl.tools import *
+from litex.gen.fhdl.tracer import get_obj_var_name
+from litex.gen.fhdl.verilog import _printexpr as verilog_printexpr
+
+
+__all__ = ["TSTriple", "Instance", "Memory",
+    "READ_FIRST", "WRITE_FIRST", "NO_CHANGE"]
+
+
+class Special(DUID):
+    def iter_expressions(self):
+        for x in []:
+            yield x
+
+    def rename_clock_domain(self, old, new):
+        for obj, attr, direction in self.iter_expressions():
+            rename_clock_domain_expr(getattr(obj, attr), old, new)
+
+    def list_clock_domains(self):
+        r = set()
+        for obj, attr, direction in self.iter_expressions():
+            r |= list_clock_domains_expr(getattr(obj, attr))
+        return r
+
+    def list_ios(self, ins, outs, inouts):
+        r = set()
+        for obj, attr, direction in self.iter_expressions():
+            if (direction == SPECIAL_INPUT and ins) \
+              or (direction == SPECIAL_OUTPUT and outs) \
+              or (direction == SPECIAL_INOUT and inouts):
+                signals = list_signals(getattr(obj, attr))
+                r.update(signals)
+        return r
+
+
+class Tristate(Special):
+    def __init__(self, target, o, oe, i=None):
+        Special.__init__(self)
+        self.target = wrap(target)
+        self.o = wrap(o)
+        self.oe = wrap(oe)
+        self.i = wrap(i) if i is not None else None
+
+    def iter_expressions(self):
+        for attr, target_context in [
+          ("target", SPECIAL_INOUT),
+          ("o", SPECIAL_INPUT),
+          ("oe", SPECIAL_INPUT),
+          ("i", SPECIAL_OUTPUT)]:
+            if getattr(self, attr) is not None:
+                yield self, attr, target_context
+
+    @staticmethod
+    def emit_verilog(tristate, ns, add_data_file):
+        def pe(e):
+            return verilog_printexpr(ns, e)[0]
+        w, s = value_bits_sign(tristate.target)
+        r = "assign " + pe(tristate.target) + " = " \
+            + pe(tristate.oe) + " ? " + pe(tristate.o) \
+            + " : " + str(w) + "'bz;\n"
+        if tristate.i is not None:
+            r += "assign " + pe(tristate.i) + " = " + pe(tristate.target) + ";\n"
+        r += "\n"
+        return r
+
+
+class TSTriple:
+    def __init__(self, bits_sign=None, min=None, max=None, reset_o=0, reset_oe=0):
+        self.o = Signal(bits_sign, min=min, max=max, reset=reset_o)
+        self.oe = Signal(reset=reset_oe)
+        self.i = Signal(bits_sign, min=min, max=max)
+
+    def get_tristate(self, target):
+        return Tristate(target, self.o, self.oe, self.i)
+
+
+class Instance(Special):
+    class _IO:
+        def __init__(self, name, expr=None):
+            self.name = name
+            if expr is None:
+                expr = Signal()
+            self.expr = wrap(expr)
+    class Input(_IO):
+        pass
+    class Output(_IO):
+        pass
+    class InOut(_IO):
+        pass
+    class Parameter:
+        def __init__(self, name, value):
+            self.name = name
+            if isinstance(value, (int, bool)):
+                value = Constant(value)
+            self.value = value
+    class PreformattedParam(str):
+        pass
+
+    def __init__(self, of, *items, name="", synthesis_directive=None, **kwargs):
+        Special.__init__(self)
+        self.of = of
+        if name:
+            self.name_override = name
+        else:
+            self.name_override = of
+        self.items = list(items)
+        self.synthesis_directive = synthesis_directive
+        for k, v in sorted(kwargs.items(), key=itemgetter(0)):
+            item_type, item_name = k.split("_", maxsplit=1)
+            item_class = {
+                "i": Instance.Input,
+                "o": Instance.Output,
+                "io": Instance.InOut,
+                "p": Instance.Parameter
+            }[item_type]
+            self.items.append(item_class(item_name, v))
+
+    def get_io(self, name):
+        for item in self.items:
+            if isinstance(item, Instance._IO) and item.name == name:
+                return item.expr
+
+    def iter_expressions(self):
+        for item in self.items:
+            if isinstance(item, Instance.Input):
+                yield item, "expr", SPECIAL_INPUT
+            elif isinstance(item, Instance.Output):
+                yield item, "expr", SPECIAL_OUTPUT
+            elif isinstance(item, Instance.InOut):
+                yield item, "expr", SPECIAL_INOUT
+
+    @staticmethod
+    def emit_verilog(instance, ns, add_data_file):
+        r = instance.of + " "
+        parameters = list(filter(lambda i: isinstance(i, Instance.Parameter), instance.items))
+        if parameters:
+            r += "#(\n"
+            firstp = True
+            for p in parameters:
+                if not firstp:
+                    r += ",\n"
+                firstp = False
+                r += "\t." + p.name + "("
+                if isinstance(p.value, Constant):
+                    r += verilog_printexpr(ns, p.value)[0]
+                elif isinstance(p.value, float):
+                    r += str(p.value)
+                elif isinstance(p.value, Instance.PreformattedParam):
+                    r += p.value
+                elif isinstance(p.value, str):
+                    r += "\"" + p.value + "\""
+                else:
+                    raise TypeError
+                r += ")"
+            r += "\n) "
+        r += ns.get_name(instance)
+        if parameters: r += " "
+        r += "(\n"
+        firstp = True
+        for p in instance.items:
+            if isinstance(p, Instance._IO):
+                name_inst = p.name
+                name_design = verilog_printexpr(ns, p.expr)[0]
+                if not firstp:
+                    r += ",\n"
+                firstp = False
+                r += "\t." + name_inst + "(" + name_design + ")"
+        if not firstp:
+            r += "\n"
+        if instance.synthesis_directive is not None:
+            synthesis_directive = "/* synthesis {} */".format(instance.synthesis_directive)
+            r += ")" + synthesis_directive + ";\n\n"
+        else:
+            r += ");\n\n"
+        return r
+
+
+(READ_FIRST, WRITE_FIRST, NO_CHANGE) = range(3)
+
+
+class _MemoryPort(Special):
+    def __init__(self, adr, dat_r, we=None, dat_w=None,
+      async_read=False, re=None, we_granularity=0, mode=WRITE_FIRST,
+      clock_domain="sys"):
+        Special.__init__(self)
+        self.adr = adr
+        self.dat_r = dat_r
+        self.we = we
+        self.dat_w = dat_w
+        self.async_read = async_read
+        self.re = re
+        self.we_granularity = we_granularity
+        self.mode = mode
+        self.clock = ClockSignal(clock_domain)
+
+    def iter_expressions(self):
+        for attr, target_context in [
+          ("adr", SPECIAL_INPUT),
+          ("we", SPECIAL_INPUT),
+          ("dat_w", SPECIAL_INPUT),
+          ("re", SPECIAL_INPUT),
+          ("dat_r", SPECIAL_OUTPUT),
+          ("clock", SPECIAL_INPUT)]:
+            yield self, attr, target_context
+
+    @staticmethod
+    def emit_verilog(port, ns, add_data_file):
+        return ""  # done by parent Memory object
+
+
+class _MemoryLocation(_Value):
+    def __init__(self, memory, index):
+        _Value.__init__(self)
+        self.memory = memory
+        self.index = wrap(index)
+
+
+class Memory(Special):
+    def __init__(self, width, depth, init=None, name=None):
+        Special.__init__(self)
+        self.width = width
+        self.depth = depth
+        self.ports = []
+        self.init = init
+        self.name_override = get_obj_var_name(name, "mem")
+
+    def __getitem__(self, index):
+        # simulation only
+        return _MemoryLocation(self, index)
+
+    def get_port(self, write_capable=False, async_read=False,
+      has_re=False, we_granularity=0, mode=WRITE_FIRST,
+      clock_domain="sys"):
+        if we_granularity >= self.width:
+            we_granularity = 0
+        adr = Signal(max=self.depth)
+        dat_r = Signal(self.width)
+        if write_capable:
+            if we_granularity:
+                we = Signal(self.width//we_granularity)
+            else:
+                we = Signal()
+            dat_w = Signal(self.width)
+        else:
+            we = None
+            dat_w = None
+        if has_re:
+            re = Signal()
+        else:
+            re = None
+        mp = _MemoryPort(adr, dat_r, we, dat_w,
+          async_read, re, we_granularity, mode,
+          clock_domain)
+        self.ports.append(mp)
+        return mp
+
+    @staticmethod
+    def emit_verilog(memory, ns, add_data_file):
+        r = ""
+        def gn(e):
+            if isinstance(e, Memory):
+                return ns.get_name(e)
+            else:
+                return verilog_printexpr(ns, e)[0]
+        adrbits = bits_for(memory.depth-1)
+
+        r += "reg [" + str(memory.width-1) + ":0] " \
+            + gn(memory) \
+            + "[0:" + str(memory.depth-1) + "];\n"
+
+        adr_regs = {}
+        data_regs = {}
+        for port in memory.ports:
+            if not port.async_read:
+                if port.mode == WRITE_FIRST and port.we is not None:
+                    adr_reg = Signal(name_override="memadr")
+                    r += "reg [" + str(adrbits-1) + ":0] " \
+                        + gn(adr_reg) + ";\n"
+                    adr_regs[id(port)] = adr_reg
+                else:
+                    data_reg = Signal(name_override="memdat")
+                    r += "reg [" + str(memory.width-1) + ":0] " \
+                        + gn(data_reg) + ";\n"
+                    data_regs[id(port)] = data_reg
+
+        for port in memory.ports:
+            r += "always @(posedge " + gn(port.clock) + ") begin\n"
+            if port.we is not None:
+                if port.we_granularity:
+                    n = memory.width//port.we_granularity
+                    for i in range(n):
+                        m = i*port.we_granularity
+                        M = (i+1)*port.we_granularity-1
+                        sl = "[" + str(M) + ":" + str(m) + "]"
+                        r += "\tif (" + gn(port.we) + "[" + str(i) + "])\n"
+                        r += "\t\t" + gn(memory) + "[" + gn(port.adr) + "]" + sl + " <= " + gn(port.dat_w) + sl + ";\n"
+                else:
+                    r += "\tif (" + gn(port.we) + ")\n"
+                    r += "\t\t" + gn(memory) + "[" + gn(port.adr) + "] <= " + gn(port.dat_w) + ";\n"
+            if not port.async_read:
+                if port.mode == WRITE_FIRST and port.we is not None:
+                    rd = "\t" + gn(adr_regs[id(port)]) + " <= " + gn(port.adr) + ";\n"
+                else:
+                    bassign = gn(data_regs[id(port)]) + " <= " + gn(memory) + "[" + gn(port.adr) + "];\n"
+                    if port.mode == READ_FIRST or port.we is None:
+                        rd = "\t" + bassign
+                    elif port.mode == NO_CHANGE:
+                        rd = "\tif (!" + gn(port.we) + ")\n" \
+                          + "\t\t" + bassign
+                if port.re is None:
+                    r += rd
+                else:
+                    r += "\tif (" + gn(port.re) + ")\n"
+                    r += "\t" + rd.replace("\n\t", "\n\t\t")
+            r += "end\n\n"
+
+        for port in memory.ports:
+            if port.async_read:
+                r += "assign " + gn(port.dat_r) + " = " + gn(memory) + "[" + gn(port.adr) + "];\n"
+            else:
+                if port.mode == WRITE_FIRST and port.we is not None:
+                    r += "assign " + gn(port.dat_r) + " = " + gn(memory) + "[" + gn(adr_regs[id(port)]) + "];\n"
+                else:
+                    r += "assign " + gn(port.dat_r) + " = " + gn(data_regs[id(port)]) + ";\n"
+        r += "\n"
+
+        if memory.init is not None:
+            content = ""
+            for d in memory.init:
+                content += "{:x}\n".format(d)
+            memory_filename = add_data_file(gn(memory) + ".init", content)
+
+            r += "initial begin\n"
+            r += "\t$readmemh(\"" + memory_filename + "\", " + gn(memory) + ");\n"
+            r += "end\n\n"
+
+        return r
+
+
+class SynthesisDirective(Special):
+    def __init__(self, template, **signals):
+        Special.__init__(self)
+        self.template = template
+        self.signals = signals
+
+    @staticmethod
+    def emit_verilog(directive, ns, add_data_file):
+        name_dict = dict((k, ns.get_name(sig)) for k, sig in directive.signals.items())
+        formatted = directive.template.format(**name_dict)
+        return "// synthesis " + formatted + "\n"
+
+
+class Keep(SynthesisDirective):
+    def __init__(self, signal):
+        SynthesisDirective.__init__(self, "attribute keep of {s} is true", s=signal)
diff --git a/litex/gen/fhdl/structure.py b/litex/gen/fhdl/structure.py
new file mode 100644 (file)
index 0000000..113954b
--- /dev/null
@@ -0,0 +1,721 @@
+import builtins as _builtins
+import collections as _collections
+
+from litex.gen.fhdl import tracer as _tracer
+from litex.gen.util.misc import flat_iteration as _flat_iteration
+
+
+class DUID:
+    """Deterministic Unique IDentifier"""
+    __next_uid = 0
+    def __init__(self):
+        self.duid = DUID.__next_uid
+        DUID.__next_uid += 1
+
+
+class _Value(DUID):
+    """Base class for operands
+
+    Instances of `_Value` or its subclasses can be operands to
+    arithmetic, comparison, bitwise, and logic operators.
+    They can be assigned (:meth:`eq`) or indexed/sliced (using the usual
+    Python indexing and slicing notation).
+
+    Values created from integers have the minimum bit width to necessary to
+    represent the integer.
+    """
+    def __bool__(self):
+        # Special case: Constants and Signals are part of a set or used as
+        # dictionary keys, and Python needs to check for equality.
+        if isinstance(self, _Operator) and self.op == "==":
+            a, b = self.operands
+            if isinstance(a, Constant) and isinstance(b, Constant):
+                return a.value == b.value
+            if isinstance(a, Signal) and isinstance(b, Signal):
+                return a is b
+            if (isinstance(a, Constant) and isinstance(b, Signal)
+                    or isinstance(a, Signal) and isinstance(a, Constant)):
+                return False
+        raise TypeError("Attempted to convert Migen value to boolean")
+
+    def __invert__(self):
+        return _Operator("~", [self])
+    def __neg__(self):
+        return _Operator("-", [self])
+
+    def __add__(self, other):
+        return _Operator("+", [self, other])
+    def __radd__(self, other):
+        return _Operator("+", [other, self])
+    def __sub__(self, other):
+        return _Operator("-", [self, other])
+    def __rsub__(self, other):
+        return _Operator("-", [other, self])
+    def __mul__(self, other):
+        return _Operator("*", [self, other])
+    def __rmul__(self, other):
+        return _Operator("*", [other, self])
+    def __lshift__(self, other):
+        return _Operator("<<<", [self, other])
+    def __rlshift__(self, other):
+        return _Operator("<<<", [other, self])
+    def __rshift__(self, other):
+        return _Operator(">>>", [self, other])
+    def __rrshift__(self, other):
+        return _Operator(">>>", [other, self])
+    def __and__(self, other):
+        return _Operator("&", [self, other])
+    def __rand__(self, other):
+        return _Operator("&", [other, self])
+    def __xor__(self, other):
+        return _Operator("^", [self, other])
+    def __rxor__(self, other):
+        return _Operator("^", [other, self])
+    def __or__(self, other):
+        return _Operator("|", [self, other])
+    def __ror__(self, other):
+        return _Operator("|", [other, self])
+
+    def __lt__(self, other):
+        return _Operator("<", [self, other])
+    def __le__(self, other):
+        return _Operator("<=", [self, other])
+    def __eq__(self, other):
+        return _Operator("==", [self, other])
+    def __ne__(self, other):
+        return _Operator("!=", [self, other])
+    def __gt__(self, other):
+        return _Operator(">", [self, other])
+    def __ge__(self, other):
+        return _Operator(">=", [self, other])
+
+    def __len__(self):
+        from litex.gen.fhdl.bitcontainer import value_bits_sign
+        return value_bits_sign(self)[0]
+
+    def __getitem__(self, key):
+        n = len(self)
+        if isinstance(key, int):
+            if key >= n:
+                raise IndexError
+            if key < 0:
+                key += n
+            return _Slice(self, key, key+1)
+        elif isinstance(key, slice):
+            start, stop, step = key.indices(n)
+            if step != 1:
+                return Cat(self[i] for i in range(start, stop, step))
+            return _Slice(self, start, stop)
+        else:
+            raise TypeError
+
+    def eq(self, r):
+        """Assignment
+
+        Parameters
+        ----------
+        r : _Value, in
+            Value to be assigned.
+
+        Returns
+        -------
+        _Assign
+            Assignment statement that can be used in combinatorial or
+            synchronous context.
+        """
+        return _Assign(self, r)
+
+    def __hash__(self):
+        raise TypeError("unhashable type: '{}'".format(type(self).__name__))
+
+
+def wrap(value):
+    """Ensures that the passed object is a Migen value. Booleans and integers
+    are automatically wrapped into ``Constant``."""
+    if isinstance(value, (bool, int)):
+        value = Constant(value)
+    if not isinstance(value, _Value):
+        raise TypeError("Object is not a Migen value")
+    return value
+
+
+class _Operator(_Value):
+    def __init__(self, op, operands):
+        _Value.__init__(self)
+        self.op = op
+        self.operands = [wrap(o) for o in operands]
+
+
+def Mux(sel, val1, val0):
+    """Multiplex between two values
+
+    Parameters
+    ----------
+    sel : _Value(1), in
+        Selector.
+    val1 : _Value(N), in
+    val0 : _Value(N), in
+        Input values.
+
+    Returns
+    -------
+    _Value(N), out
+        Output `_Value`. If `sel` is asserted, the Mux returns
+        `val1`, else `val0`.
+    """
+    return _Operator("m", [sel, val1, val0])
+
+
+class _Slice(_Value):
+    def __init__(self, value, start, stop):
+        _Value.__init__(self)
+        if not isinstance(start, int) or not isinstance(stop, int):
+            raise TypeError("Slice boundaries must be integers")
+        self.value = wrap(value)
+        self.start = start
+        self.stop = stop
+
+
+class Cat(_Value):
+    """Concatenate values
+
+    Form a compound `_Value` from several smaller ones by concatenation.
+    The first argument occupies the lower bits of the result.
+    The return value can be used on either side of an assignment, that
+    is, the concatenated value can be used as an argument on the RHS or
+    as a target on the LHS. If it is used on the LHS, it must solely
+    consist of `Signal` s, slices of `Signal` s, and other concatenations
+    meeting these properties. The bit length of the return value is the sum of
+    the bit lengths of the arguments::
+
+        len(Cat(args)) == sum(len(arg) for arg in args)
+
+    Parameters
+    ----------
+    *args : _Values or iterables of _Values, inout
+        `_Value` s to be concatenated.
+
+    Returns
+    -------
+    Cat, inout
+        Resulting `_Value` obtained by concatentation.
+    """
+    def __init__(self, *args):
+        _Value.__init__(self)
+        self.l = [wrap(v) for v in _flat_iteration(args)]
+
+
+class Replicate(_Value):
+    """Replicate a value
+
+    An input value is replicated (repeated) several times
+    to be used on the RHS of assignments::
+
+        len(Replicate(s, n)) == len(s)*n
+
+    Parameters
+    ----------
+    v : _Value, in
+        Input value to be replicated.
+    n : int
+        Number of replications.
+
+    Returns
+    -------
+    Replicate, out
+        Replicated value.
+    """
+    def __init__(self, v, n):
+        _Value.__init__(self)
+        if not isinstance(n, int) or n < 0:
+            raise TypeError("Replication count must be a positive integer")
+        self.v = wrap(v)
+        self.n = n
+
+
+class Constant(_Value):
+    """A constant, HDL-literal integer `_Value`
+
+    Parameters
+    ----------
+    value : int
+    bits_sign : int or tuple or None
+        Either an integer `bits` or a tuple `(bits, signed)`
+        specifying the number of bits in this `Constant` and whether it is
+        signed (can represent negative values). `bits_sign` defaults
+        to the minimum width and signedness of `value`.
+    """
+    def __init__(self, value, bits_sign=None):
+        from litex.gen.fhdl.bitcontainer import bits_for
+
+        _Value.__init__(self)
+
+        self.value = int(value)
+        if bits_sign is None:
+            bits_sign = bits_for(self.value), self.value < 0
+        elif isinstance(bits_sign, int):
+            bits_sign = bits_sign, self.value < 0
+        self.nbits, self.signed = bits_sign
+        if not isinstance(self.nbits, int) or self.nbits <= 0:
+            raise TypeError("Width must be a strictly positive integer")
+
+    def __hash__(self):
+        return self.value
+
+
+C = Constant  # shorthand
+
+
+class Signal(_Value):
+    """A `_Value` that can change
+
+    The `Signal` object represents a value that is expected to change
+    in the circuit. It does exactly what Verilog's `wire` and
+    `reg` and VHDL's `signal` do.
+
+    A `Signal` can be indexed to access a subset of its bits. Negative
+    indices (`signal[-1]`) and the extended Python slicing notation
+    (`signal[start:stop:step]`) are supported.
+    The indices 0 and -1 are the least and most significant bits
+    respectively.
+
+    Parameters
+    ----------
+    bits_sign : int or tuple
+        Either an integer `bits` or a tuple `(bits, signed)`
+        specifying the number of bits in this `Signal` and whether it is
+        signed (can represent negative values). `signed` defaults to
+        `False`.
+    name : str or None
+        Name hint for this signal. If `None` (default) the name is
+        inferred from the variable name this `Signal` is assigned to.
+        Name collisions are automatically resolved by prepending
+        names of objects that contain this `Signal` and by
+        appending integer sequences.
+    variable : bool
+        Deprecated.
+    reset : int
+        Reset (synchronous) or default (combinatorial) value.
+        When this `Signal` is assigned to in synchronous context and the
+        corresponding clock domain is reset, the `Signal` assumes the
+        given value. When this `Signal` is unassigned in combinatorial
+        context (due to conditional assignments not being taken),
+        the `Signal` assumes its `reset` value. Defaults to 0.
+    name_override : str or None
+        Do not use the inferred name but the given one.
+    min : int or None
+    max : int or None
+        If `bits_sign` is `None`, the signal bit width and signedness are
+        determined by the integer range given by `min` (inclusive,
+        defaults to 0) and `max` (exclusive, defaults to 2).
+    related : Signal or None
+    """
+    def __init__(self, bits_sign=None, name=None, variable=False, reset=0, name_override=None, min=None, max=None, related=None):
+        from litex.gen.fhdl.bitcontainer import bits_for
+
+        _Value.__init__(self)
+
+        # determine number of bits and signedness
+        if bits_sign is None:
+            if min is None:
+                min = 0
+            if max is None:
+                max = 2
+            max -= 1  # make both bounds inclusive
+            assert(min < max)
+            self.signed = min < 0 or max < 0
+            self.nbits = _builtins.max(bits_for(min, self.signed), bits_for(max, self.signed))
+        else:
+            assert(min is None and max is None)
+            if isinstance(bits_sign, tuple):
+                self.nbits, self.signed = bits_sign
+            else:
+                self.nbits, self.signed = bits_sign, False
+        if not isinstance(self.nbits, int) or self.nbits <= 0:
+            raise ValueError("Signal width must be a strictly positive integer")
+
+        self.variable = variable  # deprecated
+        self.reset = reset
+        self.name_override = name_override
+        self.backtrace = _tracer.trace_back(name)
+        self.related = related
+
+    def __setattr__(self, k, v):
+        if k == "reset":
+            v = wrap(v)
+        _Value.__setattr__(self, k, v)
+
+    def __repr__(self):
+        return "<Signal " + (self.backtrace[-1][0] or "anonymous") + " at " + hex(id(self)) + ">"
+
+    @classmethod
+    def like(cls, other, **kwargs):
+        """Create Signal based on another.
+
+        Parameters
+        ----------
+        other : _Value
+            Object to base this Signal on.
+
+        See `migen.fhdl.bitcontainer.value_bits_sign` for details.
+        """
+        from litex.gen.fhdl.bitcontainer import value_bits_sign
+        return cls(bits_sign=value_bits_sign(other), **kwargs)
+
+    def __hash__(self):
+        return self.duid
+
+
+class ClockSignal(_Value):
+    """Clock signal for a given clock domain
+
+    `ClockSignal` s for a given clock domain can be retrieved multiple
+    times. They all ultimately refer to the same signal.
+
+    Parameters
+    ----------
+    cd : str
+        Clock domain to obtain a clock signal for. Defaults to `"sys"`.
+    """
+    def __init__(self, cd="sys"):
+        _Value.__init__(self)
+        if not isinstance(cd, str):
+            raise TypeError("Argument of ClockSignal must be a string")
+        self.cd = cd
+
+
+class ResetSignal(_Value):
+    """Reset signal for a given clock domain
+
+    `ResetSignal` s for a given clock domain can be retrieved multiple
+    times. They all ultimately refer to the same signal.
+
+    Parameters
+    ----------
+    cd : str
+        Clock domain to obtain a reset signal for. Defaults to `"sys"`.
+    allow_reset_less : bool
+        If the clock domain is resetless, return 0 instead of reporting an
+        error.
+    """
+    def __init__(self, cd="sys", allow_reset_less=False):
+        _Value.__init__(self)
+        if not isinstance(cd, str):
+            raise TypeError("Argument of ResetSignal must be a string")
+        self.cd = cd
+        self.allow_reset_less = allow_reset_less
+
+
+# statements
+
+
+class _Statement:
+    pass
+
+
+class _Assign(_Statement):
+    def __init__(self, l, r):
+        self.l = wrap(l)
+        self.r = wrap(r)
+
+
+def _check_statement(s):
+    if isinstance(s, _collections.Iterable):
+        return all(_check_statement(ss) for ss in s)
+    else:
+        return isinstance(s, _Statement)
+
+
+class If(_Statement):
+    """Conditional execution of statements
+
+    Parameters
+    ----------
+    cond : _Value(1), in
+        Condition
+    *t : Statements
+        Statements to execute if `cond` is asserted.
+
+    Examples
+    --------
+    >>> a = Signal()
+    >>> b = Signal()
+    >>> c = Signal()
+    >>> d = Signal()
+    >>> If(a,
+    ...     b.eq(1)
+    ... ).Elif(c,
+    ...     b.eq(0)
+    ... ).Else(
+    ...     b.eq(d)
+    ... )
+    """
+    def __init__(self, cond, *t):
+        if not _check_statement(t):
+            raise TypeError("Not all test body objects are Migen statements")
+        self.cond = wrap(cond)
+        self.t = list(t)
+        self.f = []
+
+    def Else(self, *f):
+        """Add an `else` conditional block
+
+        Parameters
+        ----------
+        *f : Statements
+            Statements to execute if all previous conditions fail.
+        """
+        if not _check_statement(f):
+            raise TypeError("Not all test body objects are Migen statements")
+        _insert_else(self, list(f))
+        return self
+
+    def Elif(self, cond, *t):
+        """Add an `else if` conditional block
+
+        Parameters
+        ----------
+        cond : _Value(1), in
+            Condition
+        *t : Statements
+            Statements to execute if previous conditions fail and `cond`
+            is asserted.
+        """
+        _insert_else(self, [If(cond, *t)])
+        return self
+
+
+def _insert_else(obj, clause):
+    o = obj
+    while o.f:
+        assert(len(o.f) == 1)
+        assert(isinstance(o.f[0], If))
+        o = o.f[0]
+    o.f = clause
+
+
+class Case(_Statement):
+    """Case/Switch statement
+
+    Parameters
+    ----------
+    test : _Value, in
+        Selector value used to decide which block to execute
+    cases : dict
+        Dictionary of cases. The keys are numeric constants to compare
+        with `test`. The values are statements to be executed the
+        corresponding key matches `test`. The dictionary may contain a
+        string key `"default"` to mark a fall-through case that is
+        executed if no other key matches.
+
+    Examples
+    --------
+    >>> a = Signal()
+    >>> b = Signal()
+    >>> Case(a, {
+    ...     0:         b.eq(1),
+    ...     1:         b.eq(0),
+    ...     "default": b.eq(0),
+    ... })
+    """
+    def __init__(self, test, cases):
+        self.test = wrap(test)
+        self.cases = dict()
+        for k, v in cases.items():
+            if isinstance(k, (bool, int)):
+                k = Constant(k)
+            if (not isinstance(k, Constant) 
+                    and not (isinstance(k, str) and k == "default")):
+                raise TypeError("Case object is not a Migen constant")
+            if not isinstance(v, _collections.Iterable):
+                v = [v]
+            if not _check_statement(v):
+                raise TypeError("Not all objects for case {} "
+                                "are Migen statements".format(k))
+            self.cases[k] = v
+
+    def makedefault(self, key=None):
+        """Mark a key as the default case
+
+        Deletes/substitutes any previously existing default case.
+
+        Parameters
+        ----------
+        key : int or None
+            Key to use as default case if no other key matches.
+            By default, the largest key is the default key.
+        """
+        if key is None:
+            for choice in self.cases.keys():
+                if key is None or choice.value > key.value:
+                    key = choice
+        self.cases["default"] = self.cases[key]
+        del self.cases[key]
+        return self
+
+
+# arrays
+
+
+class _ArrayProxy(_Value):
+    def __init__(self, choices, key):
+        _Value.__init__(self)
+        self.choices = []
+        for c in choices:
+            if isinstance(c, (bool, int)):
+                c = Constant(c)
+            self.choices.append(c)
+        self.key = key
+
+    def __getattr__(self, attr):
+        return _ArrayProxy([getattr(choice, attr) for choice in self.choices],
+            self.key)
+
+    def __getitem__(self, key):
+        return _ArrayProxy([choice.__getitem__(key) for choice in self.choices],
+            self.key)
+
+
+class Array(list):
+    """Addressable multiplexer
+
+    An array is created from an iterable of values and indexed using the
+    usual Python simple indexing notation (no negative indices or
+    slices). It can be indexed by numeric constants, `_Value` s, or
+    `Signal` s.
+
+    The result of indexing the array is a proxy for the entry at the
+    given index that can be used on either RHS or LHS of assignments.
+
+    An array can be indexed multiple times.
+
+    Multidimensional arrays are supported by packing inner arrays into
+    outer arrays.
+
+    Parameters
+    ----------
+    values : iterable of ints, _Values, Signals
+        Entries of the array. Each entry can be a numeric constant, a
+        `Signal` or a `Record`.
+
+    Examples
+    --------
+    >>> a = Array(range(10))
+    >>> b = Signal(max=10)
+    >>> c = Signal(max=10)
+    >>> b.eq(a[9 - c])
+    """
+    def __getitem__(self, key):
+        if isinstance(key, Constant):
+            return list.__getitem__(self, key.value)
+        elif isinstance(key, _Value):
+            return _ArrayProxy(self, key)
+        else:
+            return list.__getitem__(self, key)
+
+
+class ClockDomain:
+    """Synchronous domain
+
+    Parameters
+    ----------
+    name : str or None
+        Domain name. If None (the default) the name is inferred from the
+        variable name this `ClockDomain` is assigned to (stripping any
+        `"cd_"` prefix).
+    reset_less : bool
+        The domain does not use a reset signal. Registers within this
+        domain are still all initialized to their reset state once, e.g.
+        through Verilog `"initial"` statements.
+
+    Attributes
+    ----------
+    clk : Signal, inout
+        The clock for this domain. Can be driven or used to drive other
+        signals (preferably in combinatorial context).
+    rst : Signal or None, inout
+        Reset signal for this domain. Can be driven or used to drive.
+    """
+    def __init__(self, name=None, reset_less=False):
+        self.name = _tracer.get_obj_var_name(name)
+        if self.name is None:
+            raise ValueError("Cannot extract clock domain name from code, need to specify.")
+        if self.name.startswith("cd_"):
+            self.name = self.name[3:]
+        if self.name[0].isdigit():
+            raise ValueError("Clock domain name cannot start with a number.")
+        self.clk = Signal(name_override=self.name + "_clk")
+        if reset_less:
+            self.rst = None
+        else:
+            self.rst = Signal(name_override=self.name + "_rst")
+
+    def rename(self, new_name):
+        """Rename the clock domain
+
+        Parameters
+        ----------
+        new_name : str
+            New name
+        """
+        self.name = new_name
+        self.clk.name_override = new_name + "_clk"
+        if self.rst is not None:
+            self.rst.name_override = new_name + "_rst"
+
+
+class _ClockDomainList(list):
+    def __getitem__(self, key):
+        if isinstance(key, str):
+            for cd in self:
+                if cd.name == key:
+                    return cd
+            raise KeyError(key)
+        else:
+            return list.__getitem__(self, key)
+
+
+(SPECIAL_INPUT, SPECIAL_OUTPUT, SPECIAL_INOUT) = range(3)
+
+
+class StopSimulation(Exception):
+    pass
+
+
+class _Fragment:
+    def __init__(self, comb=None, sync=None, specials=None, clock_domains=None, sim=None):
+        if comb is None: comb = []
+        if sync is None: sync = dict()
+        if specials is None: specials = set()
+        if clock_domains is None: clock_domains = _ClockDomainList()
+        if sim is None: sim = []
+
+        self.comb = comb
+        self.sync = sync
+        self.specials = specials
+        self.clock_domains = _ClockDomainList(clock_domains)
+        self.sim = sim
+
+    def __add__(self, other):
+        newsync = _collections.defaultdict(list)
+        for k, v in self.sync.items():
+            newsync[k] = v[:]
+        for k, v in other.sync.items():
+            newsync[k].extend(v)
+        return _Fragment(self.comb + other.comb, newsync,
+            self.specials | other.specials,
+            self.clock_domains + other.clock_domains,
+            self.sim + other.sim)
+
+    def __iadd__(self, other):
+        newsync = _collections.defaultdict(list)
+        for k, v in self.sync.items():
+            newsync[k] = v[:]
+        for k, v in other.sync.items():
+            newsync[k].extend(v)
+        self.comb += other.comb
+        self.sync = newsync
+        self.specials |= other.specials
+        self.clock_domains += other.clock_domains
+        self.sim += other.sim
+        return self
diff --git a/litex/gen/fhdl/tools.py b/litex/gen/fhdl/tools.py
new file mode 100644 (file)
index 0000000..3db9183
--- /dev/null
@@ -0,0 +1,298 @@
+from litex.gen.fhdl.structure import *
+from litex.gen.fhdl.structure import _Slice, _Assign
+from litex.gen.fhdl.visit import NodeVisitor, NodeTransformer
+from litex.gen.fhdl.bitcontainer import value_bits_sign
+from litex.gen.util.misc import flat_iteration
+
+
+class _SignalLister(NodeVisitor):
+    def __init__(self):
+        self.output_list = set()
+
+    def visit_Signal(self, node):
+        self.output_list.add(node)
+
+
+class _TargetLister(NodeVisitor):
+    def __init__(self):
+        self.output_list = set()
+        self.target_context = False
+
+    def visit_Signal(self, node):
+        if self.target_context:
+            self.output_list.add(node)
+
+    def visit_Assign(self, node):
+        self.target_context = True
+        self.visit(node.l)
+        self.target_context = False
+
+    def visit_ArrayProxy(self, node):
+        for choice in node.choices:
+            self.visit(choice)
+
+
+class _InputLister(NodeVisitor):
+    def __init__(self):
+        self.output_list = set()
+
+    def visit_Signal(self, node):
+        self.output_list.add(node)
+
+    def visit_Assign(self, node):
+        self.visit(node.r)
+
+
+def list_signals(node):
+    lister = _SignalLister()
+    lister.visit(node)
+    return lister.output_list
+
+
+def list_targets(node):
+    lister = _TargetLister()
+    lister.visit(node)
+    return lister.output_list
+
+
+def list_inputs(node):
+    lister = _InputLister()
+    lister.visit(node)
+    return lister.output_list
+
+
+def _resort_statements(ol):
+    return [statement for i, statement in
+            sorted(ol, key=lambda x: x[0])]
+
+
+def group_by_targets(sl):
+    groups = []
+    seen = set()
+    for order, stmt in enumerate(flat_iteration(sl)):
+        targets = set(list_targets(stmt))
+        group = [(order, stmt)]
+        disjoint = targets.isdisjoint(seen)
+        seen |= targets
+        if not disjoint:
+            groups, old_groups = [], groups
+            for old_targets, old_group in old_groups:
+                if targets.isdisjoint(old_targets):
+                    groups.append((old_targets, old_group))
+                else:
+                    targets |= old_targets
+                    group += old_group
+        groups.append((targets, group))
+    return [(targets, _resort_statements(stmts))
+        for targets, stmts in groups]
+
+
+def list_special_ios(f, ins, outs, inouts):
+    r = set()
+    for special in f.specials:
+        r |= special.list_ios(ins, outs, inouts)
+    return r
+
+
+class _ClockDomainLister(NodeVisitor):
+    def __init__(self):
+        self.clock_domains = set()
+
+    def visit_ClockSignal(self, node):
+        self.clock_domains.add(node.cd)
+
+    def visit_ResetSignal(self, node):
+        self.clock_domains.add(node.cd)
+
+    def visit_clock_domains(self, node):
+        for clockname, statements in node.items():
+            self.clock_domains.add(clockname)
+            self.visit(statements)
+
+
+def list_clock_domains_expr(f):
+    cdl = _ClockDomainLister()
+    cdl.visit(f)
+    return cdl.clock_domains
+
+
+def list_clock_domains(f):
+    r = list_clock_domains_expr(f)
+    for special in f.specials:
+        r |= special.list_clock_domains()
+    for cd in f.clock_domains:
+        r.add(cd.name)
+    return r
+
+
+def is_variable(node):
+    if isinstance(node, Signal):
+        return node.variable
+    elif isinstance(node, _Slice):
+        return is_variable(node.value)
+    elif isinstance(node, Cat):
+        arevars = list(map(is_variable, node.l))
+        r = arevars[0]
+        for x in arevars:
+            if x != r:
+                raise TypeError
+        return r
+    else:
+        raise TypeError
+
+
+def generate_reset(rst, sl):
+    targets = list_targets(sl)
+    return [t.eq(t.reset) for t in sorted(targets, key=lambda x: x.duid)]
+
+
+def insert_reset(rst, sl):
+    return [If(rst, *generate_reset(rst, sl)).Else(*sl)]
+
+
+def insert_resets(f):
+    newsync = dict()
+    for k, v in f.sync.items():
+        if f.clock_domains[k].rst is not None:
+            newsync[k] = insert_reset(ResetSignal(k), v)
+        else:
+            newsync[k] = v
+    f.sync = newsync
+
+
+class _Lowerer(NodeTransformer):
+    def __init__(self):
+        self.target_context = False
+        self.extra_stmts = []
+        self.comb = []
+
+    def visit_Assign(self, node):
+        old_target_context, old_extra_stmts = self.target_context, self.extra_stmts
+        self.extra_stmts = []
+
+        self.target_context = True
+        lhs = self.visit(node.l)
+        self.target_context = False
+        rhs = self.visit(node.r)
+        r = _Assign(lhs, rhs)
+        if self.extra_stmts:
+            r = [r] + self.extra_stmts
+
+        self.target_context, self.extra_stmts = old_target_context, old_extra_stmts
+        return r
+
+
+# Basics are FHDL structure elements that back-ends are not required to support
+# but can be expressed in terms of other elements (lowered) before conversion.
+class _BasicLowerer(_Lowerer):
+    def __init__(self, clock_domains):
+        self.clock_domains = clock_domains
+        _Lowerer.__init__(self)
+
+    def visit_ArrayProxy(self, node):
+        # TODO: rewrite without variables
+        array_muxed = Signal(value_bits_sign(node), variable=True)
+        if self.target_context:
+            k = self.visit(node.key)
+            cases = {}
+            for n, choice in enumerate(node.choices):
+                cases[n] = [self.visit_Assign(_Assign(choice, array_muxed))]
+            self.extra_stmts.append(Case(k, cases).makedefault())
+        else:
+            cases = dict((n, _Assign(array_muxed, self.visit(choice)))
+                for n, choice in enumerate(node.choices))
+            self.comb.append(Case(self.visit(node.key), cases).makedefault())
+        return array_muxed
+
+    def visit_ClockSignal(self, node):
+        return self.clock_domains[node.cd].clk
+
+    def visit_ResetSignal(self, node):
+        rst = self.clock_domains[node.cd].rst
+        if rst is None:
+            if node.allow_reset_less:
+                return 0
+            else:
+                raise ValueError("Attempted to get reset signal of resetless"
+                                 " domain '{}'".format(node.cd))
+        else:
+            return rst
+
+
+class _ComplexSliceLowerer(_Lowerer):
+    def visit_Slice(self, node):
+        if not isinstance(node.value, Signal):
+            slice_proxy = Signal(value_bits_sign(node.value))
+            if self.target_context:
+                a = _Assign(node.value, slice_proxy)
+            else:
+                a = _Assign(slice_proxy, node.value)
+            self.comb.append(self.visit_Assign(a))
+            node = _Slice(slice_proxy, node.start, node.stop)
+        return NodeTransformer.visit_Slice(self, node)
+
+
+def _apply_lowerer(l, f):
+    f = l.visit(f)
+    f.comb += l.comb
+
+    for special in f.specials:
+        for obj, attr, direction in special.iter_expressions():
+            if direction != SPECIAL_INOUT:
+                # inouts are only supported by Migen when connected directly to top-level
+                # in this case, they are Signal and never need lowering
+                l.comb = []
+                l.target_context = direction != SPECIAL_INPUT
+                l.extra_stmts = []
+                expr = getattr(obj, attr)
+                expr = l.visit(expr)
+                setattr(obj, attr, expr)
+                f.comb += l.comb + l.extra_stmts
+
+    return f
+
+
+def lower_basics(f):
+    return _apply_lowerer(_BasicLowerer(f.clock_domains), f)
+
+
+def lower_complex_slices(f):
+    return _apply_lowerer(_ComplexSliceLowerer(), f)
+
+
+class _ClockDomainRenamer(NodeVisitor):
+    def __init__(self, old, new):
+        self.old = old
+        self.new = new
+
+    def visit_ClockSignal(self, node):
+        if node.cd == self.old:
+            node.cd = self.new
+
+    def visit_ResetSignal(self, node):
+        if node.cd == self.old:
+            node.cd = self.new
+
+
+def rename_clock_domain_expr(f, old, new):
+    cdr = _ClockDomainRenamer(old, new)
+    cdr.visit(f)
+
+
+def rename_clock_domain(f, old, new):
+    rename_clock_domain_expr(f, old, new)
+    if new != old:
+        if old in f.sync:
+            if new in f.sync:
+                f.sync[new].extend(f.sync[old])
+            else:
+                f.sync[new] = f.sync[old]
+            del f.sync[old]
+    for special in f.specials:
+        special.rename_clock_domain(old, new)
+    try:
+        cd = f.clock_domains[old]
+    except KeyError:
+        pass
+    else:
+        cd.rename(new)
diff --git a/litex/gen/fhdl/tracer.py b/litex/gen/fhdl/tracer.py
new file mode 100644 (file)
index 0000000..a394f93
--- /dev/null
@@ -0,0 +1,115 @@
+import inspect
+from opcode import opname
+from collections import defaultdict
+
+
+def get_var_name(frame):
+    code = frame.f_code
+    call_index = frame.f_lasti
+    call_opc = opname[code.co_code[call_index]]
+    if call_opc != "CALL_FUNCTION" and call_opc != "CALL_FUNCTION_VAR":
+        return None
+    index = call_index+3
+    while True:
+        opc = opname[code.co_code[index]]
+        if opc == "STORE_NAME" or opc == "STORE_ATTR":
+            name_index = int(code.co_code[index+1])
+            return code.co_names[name_index]
+        elif opc == "STORE_FAST":
+            name_index = int(code.co_code[index+1])
+            return code.co_varnames[name_index]
+        elif opc == "STORE_DEREF":
+            name_index = int(code.co_code[index+1])
+            return code.co_cellvars[name_index]
+        elif opc == "LOAD_GLOBAL" or opc == "LOAD_ATTR" or opc == "LOAD_FAST" or opc == "LOAD_DEREF":
+            index += 3
+        elif opc == "DUP_TOP":
+            index += 1
+        elif opc == "BUILD_LIST":
+            index += 3
+        else:
+            return None
+
+
+def remove_underscore(s):
+    if len(s) > 2 and s[0] == "_" and s[1] != "_":
+        s = s[1:]
+    return s
+
+
+def get_obj_var_name(override=None, default=None):
+    if override:
+        return override
+
+    frame = inspect.currentframe().f_back
+    # We can be called via derived classes. Go back the stack frames
+    # until we reach the first class that does not inherit from us.
+    ourclass = frame.f_locals["self"].__class__
+    while "self" in frame.f_locals and isinstance(frame.f_locals["self"], ourclass):
+        frame = frame.f_back
+
+    vn = get_var_name(frame)
+    if vn is None:
+        vn = default
+    else:
+        vn = remove_underscore(vn)
+    return vn
+
+name_to_idx = defaultdict(int)
+classname_to_objs = dict()
+
+
+def index_id(l, obj):
+    for n, e in enumerate(l):
+        if id(e) == id(obj):
+            return n
+    raise ValueError
+
+
+def trace_back(varname=None):
+    l = []
+    frame = inspect.currentframe().f_back.f_back
+    while frame is not None:
+        if varname is None:
+            varname = get_var_name(frame)
+        if varname is not None:
+            varname = remove_underscore(varname)
+            l.insert(0, (varname, name_to_idx[varname]))
+            name_to_idx[varname] += 1
+
+        try:
+            obj = frame.f_locals["self"]
+        except KeyError:
+            obj = None
+        if hasattr(obj, "__del__"):
+            obj = None
+
+        if obj is None:
+            if varname is not None:
+                coname = frame.f_code.co_name
+                if coname == "<module>":
+                    modules = frame.f_globals["__name__"]
+                    modules = modules.split(".")
+                    coname = modules[len(modules)-1]
+                coname = remove_underscore(coname)
+                l.insert(0, (coname, name_to_idx[coname]))
+                name_to_idx[coname] += 1
+        else:
+            classname = obj.__class__.__name__.lower()
+            try:
+                objs = classname_to_objs[classname]
+            except KeyError:
+                classname_to_objs[classname] = [obj]
+                idx = 0
+            else:
+                try:
+                    idx = index_id(objs, obj)
+                except ValueError:
+                    idx = len(objs)
+                    objs.append(obj)
+            classname = remove_underscore(classname)
+            l.insert(0, (classname, idx))
+
+        varname = None
+        frame = frame.f_back
+    return l
diff --git a/litex/gen/fhdl/verilog.py b/litex/gen/fhdl/verilog.py
new file mode 100644 (file)
index 0000000..a0fc3b1
--- /dev/null
@@ -0,0 +1,374 @@
+from functools import partial
+from operator import itemgetter
+import collections
+
+from litex.gen.fhdl.structure import *
+from litex.gen.fhdl.structure import _Operator, _Slice, _Assign, _Fragment
+from litex.gen.fhdl.tools import *
+from litex.gen.fhdl.bitcontainer import bits_for
+from litex.gen.fhdl.namer import build_namespace
+from litex.gen.fhdl.conv_output import ConvOutput
+
+
+_reserved_keywords = {
+    "always", "and", "assign", "automatic", "begin", "buf", "bufif0", "bufif1",
+    "case", "casex", "casez", "cell", "cmos", "config", "deassign", "default",
+    "defparam", "design", "disable", "edge", "else", "end", "endcase",
+    "endconfig", "endfunction", "endgenerate", "endmodule", "endprimitive",
+    "endspecify", "endtable", "endtask", "event", "for", "force", "forever",
+    "fork", "function", "generate", "genvar", "highz0", "highz1", "if",
+    "ifnone", "incdir", "include", "initial", "inout", "input",
+    "instance", "integer", "join", "large", "liblist", "library", "localparam",
+    "macromodule", "medium", "module", "nand", "negedge", "nmos", "nor",
+    "noshowcancelled", "not", "notif0", "notif1", "or", "output", "parameter",
+    "pmos", "posedge", "primitive", "pull0", "pull1" "pulldown",
+    "pullup", "pulsestyle_onevent", "pulsestyle_ondetect", "remos", "real",
+    "realtime", "reg", "release", "repeat", "rnmos", "rpmos", "rtran",
+    "rtranif0", "rtranif1", "scalared", "showcancelled", "signed", "small",
+    "specify", "specparam", "strong0", "strong1", "supply0", "supply1",
+    "table", "task", "time", "tran", "tranif0", "tranif1", "tri", "tri0",
+    "tri1", "triand", "trior", "trireg", "unsigned", "use", "vectored", "wait",
+    "wand", "weak0", "weak1", "while", "wire", "wor","xnor", "xor"
+}
+
+
+def _printsig(ns, s):
+    if s.signed:
+        n = "signed "
+    else:
+        n = ""
+    if len(s) > 1:
+        n += "[" + str(len(s)-1) + ":0] "
+    n += ns.get_name(s)
+    return n
+
+
+def _printconstant(node):
+    if node.signed:
+        return (str(node.nbits) + "'sd" + str(2**node.nbits + node.value),
+                True)
+    else:
+        return str(node.nbits) + "'d" + str(node.value), False
+
+
+def _printexpr(ns, node):
+    if isinstance(node, Constant):
+        return _printconstant(node)
+    elif isinstance(node, Signal):
+        return ns.get_name(node), node.signed
+    elif isinstance(node, _Operator):
+        arity = len(node.operands)
+        r1, s1 = _printexpr(ns, node.operands[0])
+        if arity == 1:
+            if node.op == "-":
+                if s1:
+                    r = node.op + r1
+                else:
+                    r = "-$signed({1'd0, " + r1 + "})"
+                s = True
+            else:
+                r = node.op + r1
+                s = s1
+        elif arity == 2:
+            r2, s2 = _printexpr(ns, node.operands[1])
+            if node.op not in ["<<<", ">>>"]:
+                if s2 and not s1:
+                    r1 = "$signed({1'd0, " + r1 + "})"
+                if s1 and not s2:
+                    r2 = "$signed({1'd0, " + r2 + "})"
+            r = r1 + " " + node.op + " " + r2
+            s = s1 or s2
+        elif arity == 3:
+            assert node.op == "m"
+            r2, s2 = _printexpr(ns, node.operands[1])
+            r3, s3 = _printexpr(ns, node.operands[2])
+            if s2 and not s3:
+                r3 = "$signed({1'd0, " + r3 + "})"
+            if s3 and not s2:
+                r2 = "$signed({1'd0, " + r2 + "})"
+            r = r1 + " ? " + r2 + " : " + r3
+            s = s2 or s3
+        else:
+            raise TypeError
+        return "(" + r + ")", s
+    elif isinstance(node, _Slice):
+        # Verilog does not like us slicing non-array signals...
+        if isinstance(node.value, Signal) \
+          and len(node.value) == 1 \
+          and node.start == 0 and node.stop == 1:
+              return _printexpr(ns, node.value)
+
+        if node.start + 1 == node.stop:
+            sr = "[" + str(node.start) + "]"
+        else:
+            sr = "[" + str(node.stop-1) + ":" + str(node.start) + "]"
+        r, s = _printexpr(ns, node.value)
+        return r + sr, s
+    elif isinstance(node, Cat):
+        l = [_printexpr(ns, v)[0] for v in reversed(node.l)]
+        return "{" + ", ".join(l) + "}", False
+    elif isinstance(node, Replicate):
+        return "{" + str(node.n) + "{" + _printexpr(ns, node.v)[0] + "}}", False
+    else:
+        raise TypeError("Expression of unrecognized type: '{}'".format(type(node).__name__))
+
+
+(_AT_BLOCKING, _AT_NONBLOCKING, _AT_SIGNAL) = range(3)
+
+
+def _printnode(ns, at, level, node, target_filter=None):
+    if node is None:
+        return ""
+    elif target_filter is not None and target_filter not in list_targets(node):
+        return ""
+    elif isinstance(node, _Assign):
+        if at == _AT_BLOCKING:
+            assignment = " = "
+        elif at == _AT_NONBLOCKING:
+            assignment = " <= "
+        elif is_variable(node.l):
+            assignment = " = "
+        else:
+            assignment = " <= "
+        return "\t"*level + _printexpr(ns, node.l)[0] + assignment + _printexpr(ns, node.r)[0] + ";\n"
+    elif isinstance(node, collections.Iterable):
+        return "".join(_printnode(ns, at, level, n, target_filter) for n in node)
+    elif isinstance(node, If):
+        r = "\t"*level + "if (" + _printexpr(ns, node.cond)[0] + ") begin\n"
+        r += _printnode(ns, at, level + 1, node.t, target_filter)
+        if node.f:
+            r += "\t"*level + "end else begin\n"
+            r += _printnode(ns, at, level + 1, node.f, target_filter)
+        r += "\t"*level + "end\n"
+        return r
+    elif isinstance(node, Case):
+        if node.cases:
+            r = "\t"*level + "case (" + _printexpr(ns, node.test)[0] + ")\n"
+            css = sorted([(k, v) for (k, v) in node.cases.items() if k != "default"], key=itemgetter(0))
+            for choice, statements in css:
+                r += "\t"*(level + 1) + _printexpr(ns, choice)[0] + ": begin\n"
+                r += _printnode(ns, at, level + 2, statements, target_filter)
+                r += "\t"*(level + 1) + "end\n"
+            if "default" in node.cases:
+                r += "\t"*(level + 1) + "default: begin\n"
+                r += _printnode(ns, at, level + 2, node.cases["default"], target_filter)
+                r += "\t"*(level + 1) + "end\n"
+            r += "\t"*level + "endcase\n"
+            return r
+        else:
+            return ""
+    else:
+        raise TypeError("Node of unrecognized type: "+str(type(node)))
+
+
+def _list_comb_wires(f):
+    r = set()
+    groups = group_by_targets(f.comb)
+    for g in groups:
+        if len(g[1]) == 1 and isinstance(g[1][0], _Assign):
+            r |= g[0]
+    return r
+
+
+def _printheader(f, ios, name, ns,
+                 reg_initialization):
+    sigs = list_signals(f) | list_special_ios(f, True, True, True)
+    special_outs = list_special_ios(f, False, True, True)
+    inouts = list_special_ios(f, False, False, True)
+    targets = list_targets(f) | special_outs
+    wires = _list_comb_wires(f) | special_outs
+    r = "module " + name + "(\n"
+    firstp = True
+    for sig in sorted(ios, key=lambda x: x.duid):
+        if not firstp:
+            r += ",\n"
+        firstp = False
+        if sig in inouts:
+            r += "\tinout " + _printsig(ns, sig)
+        elif sig in targets:
+            if sig in wires:
+                r += "\toutput " + _printsig(ns, sig)
+            else:
+                r += "\toutput reg " + _printsig(ns, sig)
+        else:
+            r += "\tinput " + _printsig(ns, sig)
+    r += "\n);\n\n"
+    for sig in sorted(sigs - ios, key=lambda x: x.duid):
+        if sig in wires:
+            r += "wire " + _printsig(ns, sig) + ";\n"
+        else:
+            if reg_initialization:
+                r += "reg " + _printsig(ns, sig) + " = " + _printexpr(ns, sig.reset)[0] + ";\n"
+            else:
+                r += "reg " + _printsig(ns, sig) + ";\n"
+    r += "\n"
+    return r
+
+
+def _printcomb(f, ns,
+               display_run,
+               dummy_signal,
+               blocking_assign):
+    r = ""
+    if f.comb:
+        if dummy_signal:
+            # Generate a dummy event to get the simulator
+            # to run the combinatorial process once at the beginning.
+            syn_off = "// synthesis translate_off\n"
+            syn_on = "// synthesis translate_on\n"
+            dummy_s = Signal(name_override="dummy_s")
+            r += syn_off
+            r += "reg " + _printsig(ns, dummy_s) + ";\n"
+            r += "initial " + ns.get_name(dummy_s) + " <= 1'd0;\n"
+            r += syn_on
+
+       
+        from collections import defaultdict
+
+        target_stmt_map = defaultdict(list)
+
+        for statement in flat_iteration(f.comb):
+            targets = list_targets(statement)
+            for t in targets:
+                target_stmt_map[t].append(statement)
+
+        #from pprint import pprint
+        #pprint(target_stmt_map)
+
+        groups = group_by_targets(f.comb)
+
+        for n, (t, stmts) in enumerate(target_stmt_map.items()):
+            assert isinstance(t, Signal)
+            if len(stmts) == 1 and isinstance(stmts[0], _Assign):
+                r += "assign " + _printnode(ns, _AT_BLOCKING, 0, stmts[0])
+            else:
+                if dummy_signal:
+                    dummy_d = Signal(name_override="dummy_d")
+                    r += "\n" + syn_off
+                    r += "reg " + _printsig(ns, dummy_d) + ";\n"
+                    r += syn_on
+
+                r += "always @(*) begin\n"
+                if display_run:
+                    r += "\t$display(\"Running comb block #" + str(n) + "\");\n"
+                if blocking_assign:
+                       r += "\t" + ns.get_name(t) + " = " + _printexpr(ns, t.reset)[0] + ";\n"
+                       r += _printnode(ns, _AT_BLOCKING, 1, stmts, t)
+                else:
+                       r += "\t" + ns.get_name(t) + " <= " + _printexpr(ns, t.reset)[0] + ";\n"
+                       r += _printnode(ns, _AT_NONBLOCKING, 1, stmts, t)
+                if dummy_signal:
+                       r += syn_off
+                       r += "\t" + ns.get_name(dummy_d) + " = " + ns.get_name(dummy_s) + ";\n"
+                       r += syn_on
+                r += "end\n"
+    r += "\n"
+    return r
+
+
+def _printsync(f, ns):
+    r = ""
+    for k, v in sorted(f.sync.items(), key=itemgetter(0)):
+        r += "always @(posedge " + ns.get_name(f.clock_domains[k].clk) + ") begin\n"
+        r += _printnode(ns, _AT_SIGNAL, 1, v)
+        r += "end\n\n"
+    return r
+
+
+def _call_special_classmethod(overrides, obj, method, *args, **kwargs):
+    cl = obj.__class__
+    if cl in overrides:
+        cl = overrides[cl]
+    if hasattr(cl, method):
+        return getattr(cl, method)(obj, *args, **kwargs)
+    else:
+        return None
+
+
+def _lower_specials_step(overrides, specials):
+    f = _Fragment()
+    lowered_specials = set()
+    for special in sorted(specials, key=lambda x: x.duid):
+        impl = _call_special_classmethod(overrides, special, "lower")
+        if impl is not None:
+            f += impl.get_fragment()
+            lowered_specials.add(special)
+    return f, lowered_specials
+
+
+def _can_lower(overrides, specials):
+    for special in specials:
+        cl = special.__class__
+        if cl in overrides:
+            cl = overrides[cl]
+        if hasattr(cl, "lower"):
+            return True
+    return False
+
+
+def _lower_specials(overrides, specials):
+    f, lowered_specials = _lower_specials_step(overrides, specials)
+    while _can_lower(overrides, f.specials):
+        f2, lowered_specials2 = _lower_specials_step(overrides, f.specials)
+        f += f2
+        lowered_specials |= lowered_specials2
+        f.specials -= lowered_specials2
+    return f, lowered_specials
+
+
+def _printspecials(overrides, specials, ns, add_data_file):
+    r = ""
+    for special in sorted(specials, key=lambda x: x.duid):
+        pr = _call_special_classmethod(overrides, special, "emit_verilog", ns, add_data_file)
+        if pr is None:
+            raise NotImplementedError("Special " + str(special) + " failed to implement emit_verilog")
+        r += pr
+    return r
+
+
+def convert(f, ios=None, name="top",
+  special_overrides=dict(),
+  create_clock_domains=True,
+  display_run=False, asic_syntax=False):
+    r = ConvOutput()
+    if not isinstance(f, _Fragment):
+        f = f.get_fragment()
+    if ios is None:
+        ios = set()
+
+    for cd_name in sorted(list_clock_domains(f)):
+        try:
+            f.clock_domains[cd_name]
+        except KeyError:
+            if create_clock_domains:
+                cd = ClockDomain(cd_name)
+                f.clock_domains.append(cd)
+                ios |= {cd.clk, cd.rst}
+            else:
+                raise KeyError("Unresolved clock domain: '"+cd_name+"'")
+
+    f = lower_complex_slices(f)
+    insert_resets(f)
+    f = lower_basics(f)
+    fs, lowered_specials = _lower_specials(special_overrides, f.specials)
+    f += lower_basics(fs)
+
+    ns = build_namespace(list_signals(f) \
+        | list_special_ios(f, True, True, True) \
+        | ios, _reserved_keywords)
+    ns.clock_domains = f.clock_domains
+    r.ns = ns
+
+    src = "/* Machine-generated using Migen */\n"
+    src += _printheader(f, ios, name, ns,
+                        reg_initialization=not asic_syntax)
+    src += _printcomb(f, ns,
+                      display_run=display_run,
+                      dummy_signal=not asic_syntax,
+                      blocking_assign=asic_syntax)
+    src += _printsync(f, ns)
+    src += _printspecials(special_overrides, f.specials - lowered_specials, ns, r.add_data_file)
+    src += "endmodule\n"
+    r.set_main_source(src)
+
+    return r
diff --git a/litex/gen/fhdl/visit.py b/litex/gen/fhdl/visit.py
new file mode 100644 (file)
index 0000000..0ccd509
--- /dev/null
@@ -0,0 +1,202 @@
+from copy import copy
+
+from litex.gen.fhdl.structure import *
+from litex.gen.fhdl.structure import (_Operator, _Slice, _Assign, _ArrayProxy,
+                                  _Fragment)
+
+
+class NodeVisitor:
+    def visit(self, node):
+        if isinstance(node, Constant):
+            self.visit_Constant(node)
+        elif isinstance(node, Signal):
+            self.visit_Signal(node)
+        elif isinstance(node, ClockSignal):
+            self.visit_ClockSignal(node)
+        elif isinstance(node, ResetSignal):
+            self.visit_ResetSignal(node)
+        elif isinstance(node, _Operator):
+            self.visit_Operator(node)
+        elif isinstance(node, _Slice):
+            self.visit_Slice(node)
+        elif isinstance(node, Cat):
+            self.visit_Cat(node)
+        elif isinstance(node, Replicate):
+            self.visit_Replicate(node)
+        elif isinstance(node, _Assign):
+            self.visit_Assign(node)
+        elif isinstance(node, If):
+            self.visit_If(node)
+        elif isinstance(node, Case):
+            self.visit_Case(node)
+        elif isinstance(node, _Fragment):
+            self.visit_Fragment(node)
+        elif isinstance(node, (list, tuple)):
+            self.visit_statements(node)
+        elif isinstance(node, dict):
+            self.visit_clock_domains(node)
+        elif isinstance(node, _ArrayProxy):
+            self.visit_ArrayProxy(node)
+        elif node is not None:
+            self.visit_unknown(node)
+
+    def visit_Constant(self, node):
+        pass
+
+    def visit_Signal(self, node):
+        pass
+
+    def visit_ClockSignal(self, node):
+        pass
+
+    def visit_ResetSignal(self, node):
+        pass
+
+    def visit_Operator(self, node):
+        for o in node.operands:
+            self.visit(o)
+
+    def visit_Slice(self, node):
+        self.visit(node.value)
+
+    def visit_Cat(self, node):
+        for e in node.l:
+            self.visit(e)
+
+    def visit_Replicate(self, node):
+        self.visit(node.v)
+
+    def visit_Assign(self, node):
+        self.visit(node.l)
+        self.visit(node.r)
+
+    def visit_If(self, node):
+        self.visit(node.cond)
+        self.visit(node.t)
+        self.visit(node.f)
+
+    def visit_Case(self, node):
+        self.visit(node.test)
+        for v, statements in node.cases.items():
+            self.visit(statements)
+
+    def visit_Fragment(self, node):
+        self.visit(node.comb)
+        self.visit(node.sync)
+
+    def visit_statements(self, node):
+        for statement in node:
+            self.visit(statement)
+
+    def visit_clock_domains(self, node):
+        for clockname, statements in node.items():
+            self.visit(statements)
+
+    def visit_ArrayProxy(self, node):
+        for choice in node.choices:
+            self.visit(choice)
+        self.visit(node.key)
+
+    def visit_unknown(self, node):
+        pass
+
+
+# Default methods always copy the node, except for:
+# - Signals, ClockSignals and ResetSignals
+# - Unknown objects
+# - All fragment fields except comb and sync
+# In those cases, the original node is returned unchanged.
+class NodeTransformer:
+    def visit(self, node):
+        if isinstance(node, Constant):
+            return self.visit_Constant(node)
+        elif isinstance(node, Signal):
+            return self.visit_Signal(node)
+        elif isinstance(node, ClockSignal):
+            return self.visit_ClockSignal(node)
+        elif isinstance(node, ResetSignal):
+            return self.visit_ResetSignal(node)
+        elif isinstance(node, _Operator):
+            return self.visit_Operator(node)
+        elif isinstance(node, _Slice):
+            return self.visit_Slice(node)
+        elif isinstance(node, Cat):
+            return self.visit_Cat(node)
+        elif isinstance(node, Replicate):
+            return self.visit_Replicate(node)
+        elif isinstance(node, _Assign):
+            return self.visit_Assign(node)
+        elif isinstance(node, If):
+            return self.visit_If(node)
+        elif isinstance(node, Case):
+            return self.visit_Case(node)
+        elif isinstance(node, _Fragment):
+            return self.visit_Fragment(node)
+        elif isinstance(node, (list, tuple)):
+            return self.visit_statements(node)
+        elif isinstance(node, dict):
+            return self.visit_clock_domains(node)
+        elif isinstance(node, _ArrayProxy):
+            return self.visit_ArrayProxy(node)
+        elif node is not None:
+            return self.visit_unknown(node)
+        else:
+            return None
+
+    def visit_Constant(self, node):
+        return node
+
+    def visit_Signal(self, node):
+        return node
+
+    def visit_ClockSignal(self, node):
+        return node
+
+    def visit_ResetSignal(self, node):
+        return node
+
+    def visit_Operator(self, node):
+        return _Operator(node.op, [self.visit(o) for o in node.operands])
+
+    def visit_Slice(self, node):
+        return _Slice(self.visit(node.value), node.start, node.stop)
+
+    def visit_Cat(self, node):
+        return Cat(*[self.visit(e) for e in node.l])
+
+    def visit_Replicate(self, node):
+        return Replicate(self.visit(node.v), node.n)
+
+    def visit_Assign(self, node):
+        return _Assign(self.visit(node.l), self.visit(node.r))
+
+    def visit_If(self, node):
+        r = If(self.visit(node.cond))
+        r.t = self.visit(node.t)
+        r.f = self.visit(node.f)
+        return r
+
+    def visit_Case(self, node):
+        cases = dict((v, self.visit(statements)) for v, statements in node.cases.items())
+        r = Case(self.visit(node.test), cases)
+        return r
+
+    def visit_Fragment(self, node):
+        r = copy(node)
+        r.comb = self.visit(node.comb)
+        r.sync = self.visit(node.sync)
+        return r
+
+    # NOTE: this will always return a list, even if node is a tuple
+    def visit_statements(self, node):
+        return [self.visit(statement) for statement in node]
+
+    def visit_clock_domains(self, node):
+        return dict((clockname, self.visit(statements)) for clockname, statements in node.items())
+
+    def visit_ArrayProxy(self, node):
+        return _ArrayProxy([self.visit(choice) for choice in node.choices],
+            self.visit(node.key))
+
+    def visit_unknown(self, node):
+        return node
diff --git a/litex/gen/genlib/__init__.py b/litex/gen/genlib/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/litex/gen/genlib/cdc.py b/litex/gen/genlib/cdc.py
new file mode 100644 (file)
index 0000000..90b5f90
--- /dev/null
@@ -0,0 +1,141 @@
+from litex.gen.fhdl.structure import *
+from litex.gen.fhdl.module import Module
+from litex.gen.fhdl.specials import Special
+from litex.gen.fhdl.bitcontainer import value_bits_sign
+from litex.gen.genlib.misc import WaitTimer
+
+
+class NoRetiming(Special):
+    def __init__(self, reg):
+        Special.__init__(self)
+        self.reg = reg
+
+    # do nothing
+    @staticmethod
+    def lower(dr):
+        return Module()
+
+
+class MultiRegImpl(Module):
+    def __init__(self, i, o, odomain, n):
+        self.i = i
+        self.o = o
+        self.odomain = odomain
+
+        w, signed = value_bits_sign(self.i)
+        self.regs = [Signal((w, signed)) for i in range(n)]
+
+        ###
+
+        src = self.i
+        for reg in self.regs:
+            sd = getattr(self.sync, self.odomain)
+            sd += reg.eq(src)
+            src = reg
+        self.comb += self.o.eq(src)
+        self.specials += [NoRetiming(reg) for reg in self.regs]
+
+
+class MultiReg(Special):
+    def __init__(self, i, o, odomain="sys", n=2):
+        Special.__init__(self)
+        self.i = wrap(i)
+        self.o = wrap(o)
+        self.odomain = odomain
+        self.n = n
+
+    def iter_expressions(self):
+        yield self, "i", SPECIAL_INPUT
+        yield self, "o", SPECIAL_OUTPUT
+
+    def rename_clock_domain(self, old, new):
+        Special.rename_clock_domain(self, old, new)
+        if self.odomain == old:
+            self.odomain = new
+
+    def list_clock_domains(self):
+        r = Special.list_clock_domains(self)
+        r.add(self.odomain)
+        return r
+
+    @staticmethod
+    def lower(dr):
+        return MultiRegImpl(dr.i, dr.o, dr.odomain, dr.n)
+
+
+class PulseSynchronizer(Module):
+    def __init__(self, idomain, odomain):
+        self.i = Signal()
+        self.o = Signal()
+
+        ###
+
+        toggle_i = Signal()
+        toggle_o = Signal()
+        toggle_o_r = Signal()
+
+        sync_i = getattr(self.sync, idomain)
+        sync_o = getattr(self.sync, odomain)
+
+        sync_i += If(self.i, toggle_i.eq(~toggle_i))
+        self.specials += MultiReg(toggle_i, toggle_o, odomain)
+        sync_o += toggle_o_r.eq(toggle_o)
+        self.comb += self.o.eq(toggle_o ^ toggle_o_r)
+
+
+class BusSynchronizer(Module):
+    """Clock domain transfer of several bits at once.
+
+    Ensures that all the bits form a single word that was present
+    synchronously in the input clock domain (unlike direct use of
+    ``MultiReg``)."""
+    def __init__(self, width, idomain, odomain, timeout=128):
+        self.i = Signal(width)
+        self.o = Signal(width)
+
+        if width == 1:
+            self.specials += MultiReg(self.i, self.o, odomain)
+        else:
+            sync_i = getattr(self.sync, idomain)
+            sync_o = getattr(self.sync, odomain)
+
+            starter = Signal(reset=1)
+            sync_i += starter.eq(0)
+            self.submodules._ping = PulseSynchronizer(idomain, odomain)
+            self.submodules._pong = PulseSynchronizer(odomain, idomain)
+            self.submodules._timeout = WaitTimer(timeout)
+            self.comb += [
+                self._timeout.wait.eq(~self._ping.i),
+                self._ping.i.eq(starter | self._pong.o | self._timeout.done),
+                self._pong.i.eq(self._ping.i)
+            ]
+
+            ibuffer = Signal(width)
+            obuffer = Signal(width)
+            sync_i += If(self._pong.o, ibuffer.eq(self.i))
+            self.specials += MultiReg(ibuffer, obuffer, odomain)
+            sync_o += If(self._ping.o, self.o.eq(obuffer))
+
+
+class GrayCounter(Module):
+    def __init__(self, width):
+        self.ce = Signal()
+        self.q = Signal(width)
+        self.q_next = Signal(width)
+        self.q_binary = Signal(width)
+        self.q_next_binary = Signal(width)
+
+        ###
+
+        self.comb += [
+            If(self.ce,
+                self.q_next_binary.eq(self.q_binary + 1)
+            ).Else(
+                self.q_next_binary.eq(self.q_binary)
+            ),
+            self.q_next.eq(self.q_next_binary ^ self.q_next_binary[1:])
+        ]
+        self.sync += [
+            self.q_binary.eq(self.q_next_binary),
+            self.q.eq(self.q_next)
+        ]
diff --git a/litex/gen/genlib/coding.py b/litex/gen/genlib/coding.py
new file mode 100644 (file)
index 0000000..80d62d6
--- /dev/null
@@ -0,0 +1,98 @@
+"""
+Encoders and decoders between binary and one-hot representation
+"""
+
+from litex.gen.fhdl.structure import *
+from litex.gen.fhdl.module import Module
+
+
+class Encoder(Module):
+    """Encode one-hot to binary
+
+    If `n` is low, the `o` th bit in `i` is asserted, else none or
+    multiple bits are asserted.
+
+    Parameters
+    ----------
+    width : int
+        Bit width of the input
+
+    Attributes
+    ----------
+    i : Signal(width), in
+        One-hot input
+    o : Signal(max=width), out
+        Encoded binary
+    n : Signal(1), out
+        Invalid, either none or multiple input bits are asserted
+    """
+    def __init__(self, width):
+        self.i = Signal(width)  # one-hot
+        self.o = Signal(max=max(2, width))  # binary
+        self.n = Signal()  # invalid: none or multiple
+        act = dict((1<<j, self.o.eq(j)) for j in range(width))
+        act["default"] = self.n.eq(1)
+        self.comb += Case(self.i, act)
+
+
+class PriorityEncoder(Module):
+    """Priority encode requests to binary
+
+    If `n` is low, the `o` th bit in `i` is asserted and the bits below
+    `o` are unasserted, else `o == 0`. The LSB has priority.
+
+    Parameters
+    ----------
+    width : int
+        Bit width of the input
+
+    Attributes
+    ----------
+    i : Signal(width), in
+        Input requests
+    o : Signal(max=width), out
+        Encoded binary
+    n : Signal(1), out
+        Invalid, no input bits are asserted
+    """
+    def __init__(self, width):
+        self.i = Signal(width)  # one-hot, lsb has priority
+        self.o = Signal(max=max(2, width))  # binary
+        self.n = Signal()  # none
+        for j in range(width)[::-1]:  # last has priority
+            self.comb += If(self.i[j], self.o.eq(j))
+        self.comb += self.n.eq(self.i == 0)
+
+
+class Decoder(Module):
+    """Decode binary to one-hot
+
+    If `n` is low, the `i` th bit in `o` is asserted, the others are
+    not, else `o == 0`.
+
+    Parameters
+    ----------
+    width : int
+        Bit width of the output
+
+    Attributes
+    ----------
+    i : Signal(max=width), in
+        Input binary
+    o : Signal(width), out
+        Decoded one-hot
+    n : Signal(1), in
+        Invalid, no output bits are to be asserted
+    """
+
+    def __init__(self, width):
+        self.i = Signal(max=max(2, width))  # binary
+        self.n = Signal()  # none/invalid
+        self.o = Signal(width)  # one-hot
+        act = dict((j, self.o.eq(1<<j)) for j in range(width))
+        self.comb += Case(self.i, act)
+        self.comb += If(self.n, self.o.eq(0))
+
+
+class PriorityDecoder(Decoder):
+    pass  # same
diff --git a/litex/gen/genlib/divider.py b/litex/gen/genlib/divider.py
new file mode 100644 (file)
index 0000000..841e7ad
--- /dev/null
@@ -0,0 +1,40 @@
+from litex.gen.fhdl.structure import *
+from litex.gen.fhdl.module import Module
+
+
+class Divider(Module):
+    def __init__(self, w):
+        self.start_i = Signal()
+        self.dividend_i = Signal(w)
+        self.divisor_i = Signal(w)
+        self.ready_o = Signal()
+        self.quotient_o = Signal(w)
+        self.remainder_o = Signal(w)
+
+        ###
+
+        qr = Signal(2*w)
+        counter = Signal(max=w+1)
+        divisor_r = Signal(w)
+        diff = Signal(w+1)
+
+        self.comb += [
+            self.quotient_o.eq(qr[:w]),
+            self.remainder_o.eq(qr[w:]),
+            self.ready_o.eq(counter == 0),
+            diff.eq(qr[w-1:] - divisor_r)
+        ]
+        self.sync += [
+            If(self.start_i,
+                counter.eq(w),
+                qr.eq(self.dividend_i),
+                divisor_r.eq(self.divisor_i)
+            ).Elif(~self.ready_o,
+                    If(diff[w],
+                        qr.eq(Cat(0, qr[:2*w-1]))
+                    ).Else(
+                        qr.eq(Cat(1, qr[:w-1], diff[:w]))
+                    ),
+                    counter.eq(counter - 1)
+            )
+        ]
diff --git a/litex/gen/genlib/fifo.py b/litex/gen/genlib/fifo.py
new file mode 100644 (file)
index 0000000..167f964
--- /dev/null
@@ -0,0 +1,214 @@
+from litex.gen.fhdl.structure import *
+from litex.gen.fhdl.module import Module
+from litex.gen.fhdl.specials import Memory
+from litex.gen.fhdl.bitcontainer import log2_int
+from litex.gen.fhdl.decorators import ClockDomainsRenamer
+from litex.gen.genlib.cdc import NoRetiming, MultiReg, GrayCounter
+
+
+def _inc(signal, modulo):
+    if modulo == 2**len(signal):
+        return signal.eq(signal + 1)
+    else:
+        return If(signal == (modulo - 1),
+            signal.eq(0)
+        ).Else(
+            signal.eq(signal + 1)
+        )
+
+
+class _FIFOInterface:
+    """
+    Data written to the input interface (`din`, `we`, `writable`) is
+    buffered and can be read at the output interface (`dout`, `re`,
+    `readable`). The data entry written first to the input
+    also appears first on the output.
+
+    Parameters
+    ----------
+    width : int
+        Bit width for the data.
+    depth : int
+        Depth of the FIFO.
+
+    Attributes
+    ----------
+    din : in, width
+        Input data
+    writable : out
+        There is space in the FIFO and `we` can be asserted to load new data.
+    we : in
+        Write enable signal to latch `din` into the FIFO. Does nothing if
+        `writable` is not asserted.
+    dout : out, width
+        Output data. Only valid if `readable` is asserted.
+    readable : out
+        Output data `dout` valid, FIFO not empty.
+    re : in
+        Acknowledge `dout`. If asserted, the next entry will be
+        available on the next cycle (if `readable` is high then).
+    """
+    def __init__(self, width, depth):
+        self.we = Signal()
+        self.writable = Signal()  # not full
+        self.re = Signal()
+        self.readable = Signal()  # not empty
+
+        self.din = Signal(width)
+        self.dout = Signal(width)
+        self.width = width
+
+
+class SyncFIFO(Module, _FIFOInterface):
+    """Synchronous FIFO (first in, first out)
+
+    Read and write interfaces are accessed from the same clock domain.
+    If different clock domains are needed, use :class:`AsyncFIFO`.
+
+    {interface}
+    level : out
+        Number of unread entries.
+    replace : in
+        Replaces the last entry written into the FIFO with `din`. Does nothing
+        if that entry has already been read (i.e. the FIFO is empty).
+        Assert in conjunction with `we`.
+    """
+    __doc__ = __doc__.format(interface=_FIFOInterface.__doc__)
+
+    def __init__(self, width, depth, fwft=True):
+        _FIFOInterface.__init__(self, width, depth)
+
+        self.level = Signal(max=depth+1)
+        self.replace = Signal()
+
+        ###
+
+        produce = Signal(max=depth)
+        consume = Signal(max=depth)
+        storage = Memory(self.width, depth)
+        self.specials += storage
+
+        wrport = storage.get_port(write_capable=True)
+        self.specials += wrport
+        self.comb += [
+            If(self.replace,
+                wrport.adr.eq(produce-1)
+            ).Else(
+                wrport.adr.eq(produce)
+            ),
+            wrport.dat_w.eq(self.din),
+            wrport.we.eq(self.we & (self.writable | self.replace))
+        ]
+        self.sync += If(self.we & self.writable & ~self.replace,
+            _inc(produce, depth))
+
+        do_read = Signal()
+        self.comb += do_read.eq(self.readable & self.re)
+
+        rdport = storage.get_port(async_read=fwft, has_re=not fwft)
+        self.specials += rdport
+        self.comb += [
+            rdport.adr.eq(consume),
+            self.dout.eq(rdport.dat_r)
+        ]
+        if not fwft:
+            self.comb += rdport.re.eq(do_read)
+        self.sync += If(do_read, _inc(consume, depth))
+
+        self.sync += \
+            If(self.we & self.writable & ~self.replace,
+                If(~do_read, self.level.eq(self.level + 1))
+            ).Elif(do_read,
+                self.level.eq(self.level - 1)
+            )
+        self.comb += [
+            self.writable.eq(self.level != depth),
+            self.readable.eq(self.level != 0)
+        ]
+
+
+class SyncFIFOBuffered(Module, _FIFOInterface):
+    def __init__(self, width, depth):
+        _FIFOInterface.__init__(self, width, depth)
+        self.submodules.fifo = fifo = SyncFIFO(width, depth, False)
+
+        self.writable = fifo.writable
+        self.din = fifo.din
+        self.we = fifo.we
+        self.dout = fifo.dout
+        self.level = Signal(max=depth+2)
+
+        ###
+
+        self.comb += fifo.re.eq(fifo.readable & (~self.readable | self.re))
+        self.sync += \
+            If(fifo.re,
+                self.readable.eq(1),
+            ).Elif(self.re,
+                self.readable.eq(0),
+            )
+        self.comb += self.level.eq(fifo.level + self.readable)
+
+
+class AsyncFIFO(Module, _FIFOInterface):
+    """Asynchronous FIFO (first in, first out)
+
+    Read and write interfaces are accessed from different clock domains,
+    named `read` and `write`. Use `ClockDomainsRenamer` to rename to
+    other names.
+
+    {interface}
+    """
+    __doc__ = __doc__.format(interface=_FIFOInterface.__doc__)
+
+    def __init__(self, width, depth):
+        _FIFOInterface.__init__(self, width, depth)
+
+        ###
+
+        depth_bits = log2_int(depth, True)
+
+        produce = ClockDomainsRenamer("write")(GrayCounter(depth_bits+1))
+        consume = ClockDomainsRenamer("read")(GrayCounter(depth_bits+1))
+        self.submodules += produce, consume
+        self.comb += [
+            produce.ce.eq(self.writable & self.we),
+            consume.ce.eq(self.readable & self.re)
+        ]
+
+        produce_rdomain = Signal(depth_bits+1)
+        self.specials += [
+            NoRetiming(produce.q),
+            MultiReg(produce.q, produce_rdomain, "read")
+        ]
+        consume_wdomain = Signal(depth_bits+1)
+        self.specials += [
+            NoRetiming(consume.q),
+            MultiReg(consume.q, consume_wdomain, "write")
+        ]
+        if depth_bits == 1:
+            self.comb += self.writable.eq((produce.q[-1] == consume_wdomain[-1])
+                | (produce.q[-2] == consume_wdomain[-2]))
+        else:
+            self.comb += [
+                self.writable.eq((produce.q[-1] == consume_wdomain[-1])
+                | (produce.q[-2] == consume_wdomain[-2])
+                | (produce.q[:-2] != consume_wdomain[:-2]))
+            ]
+        self.comb += self.readable.eq(consume.q != produce_rdomain)
+
+        storage = Memory(self.width, depth)
+        self.specials += storage
+        wrport = storage.get_port(write_capable=True, clock_domain="write")
+        self.specials += wrport
+        self.comb += [
+            wrport.adr.eq(produce.q_binary[:-1]),
+            wrport.dat_w.eq(self.din),
+            wrport.we.eq(produce.ce)
+        ]
+        rdport = storage.get_port(clock_domain="read")
+        self.specials += rdport
+        self.comb += [
+            rdport.adr.eq(consume.q_next_binary[:-1]),
+            self.dout.eq(rdport.dat_r)
+        ]
diff --git a/litex/gen/genlib/fsm.py b/litex/gen/genlib/fsm.py
new file mode 100644 (file)
index 0000000..de20e74
--- /dev/null
@@ -0,0 +1,176 @@
+from collections import OrderedDict
+
+from litex.gen.fhdl.structure import *
+from litex.gen.fhdl.structure import _Statement, _Slice, _ArrayProxy
+from litex.gen.fhdl.module import Module, FinalizeError
+from litex.gen.fhdl.visit import NodeTransformer
+from litex.gen.fhdl.bitcontainer import value_bits_sign
+
+
+__all__ = ["AnonymousState", "NextState", "NextValue", "FSM"]
+
+
+class AnonymousState:
+    pass
+
+
+# do not use namedtuple here as it inherits tuple
+# and the latter is used elsewhere in FHDL
+class NextState(_Statement):
+    def __init__(self, state):
+        self.state = state
+
+
+class NextValue(_Statement):
+    def __init__(self, target, value):
+        self.target = target
+        self.value = value
+
+
+def _target_eq(a, b):
+    if type(a) != type(b):
+        return False
+    ty = type(a)
+    if ty == Constant:
+        return a.value == b.value
+    elif ty == Signal:
+        return a is b
+    elif ty == Cat:
+        return all(_target_eq(x, y) for x, y in zip(a.l, b.l))
+    elif ty == _Slice:
+        return (_target_eq(a.value, b.value)
+                    and a.start == b.start
+                    and a.end == b.end)
+    elif ty == _ArrayProxy:
+        return (all(_target_eq(x, y) for x, y in zip(a.choices, b.choices))
+                    and _target_eq(a.key, b.key))
+    else:
+        raise ValueError("NextValue cannot be used with target type '{}'"
+                         .format(ty))
+
+
+class _LowerNext(NodeTransformer):
+    def __init__(self, next_state_signal, encoding, aliases):
+        self.next_state_signal = next_state_signal
+        self.encoding = encoding
+        self.aliases = aliases
+        # (target, next_value_ce, next_value)
+        self.registers = []
+
+    def _get_register_control(self, target):
+        for x in self.registers:
+            if _target_eq(target, x[0]):
+                return x[1], x[2]
+        raise KeyError
+
+    def visit_unknown(self, node):
+        if isinstance(node, NextState):
+            try:
+                actual_state = self.aliases[node.state]
+            except KeyError:
+                actual_state = node.state
+            return self.next_state_signal.eq(self.encoding[actual_state])
+        elif isinstance(node, NextValue):
+            try:
+                next_value_ce, next_value = self._get_register_control(node.target)
+            except KeyError:
+                related = node.target if isinstance(node.target, Signal) else None
+                next_value = Signal(bits_sign=value_bits_sign(node.target), related=related)
+                next_value_ce = Signal(related=related)
+                self.registers.append((node.target, next_value_ce, next_value))
+            return next_value.eq(node.value), next_value_ce.eq(1)
+        else:
+            return node
+
+
+class FSM(Module):
+    def __init__(self, reset_state=None):
+        self.actions = OrderedDict()
+        self.state_aliases = dict()
+        self.reset_state = reset_state
+
+        self.before_entering_signals = OrderedDict()
+        self.before_leaving_signals = OrderedDict()
+        self.after_entering_signals = OrderedDict()
+        self.after_leaving_signals = OrderedDict()
+
+    def act(self, state, *statements):
+        if self.finalized:
+            raise FinalizeError
+        if self.reset_state is None:
+            self.reset_state = state
+        if state not in self.actions:
+            self.actions[state] = []
+        self.actions[state] += statements
+
+    def delayed_enter(self, name, target, delay):
+        if self.finalized:
+            raise FinalizeError
+        if delay > 0:
+            state = name
+            for i in range(delay):
+                if i == delay - 1:
+                    next_state = target
+                else:
+                    next_state = AnonymousState()
+                self.act(state, NextState(next_state))
+                state = next_state
+        else:
+            self.state_aliases[name] = target
+
+    def ongoing(self, state):
+        is_ongoing = Signal()
+        self.act(state, is_ongoing.eq(1))
+        return is_ongoing
+
+    def _get_signal(self, d, state):
+        if state not in self.actions:
+            self.actions[state] = []
+        try:
+            return d[state]
+        except KeyError:
+            is_el = Signal()
+            d[state] = is_el
+            return is_el
+
+    def before_entering(self, state):
+        return self._get_signal(self.before_entering_signals, state)
+
+    def before_leaving(self, state):
+        return self._get_signal(self.before_leaving_signals, state)
+
+    def after_entering(self, state):
+        signal = self._get_signal(self.after_entering_signals, state)
+        self.sync += signal.eq(self.before_entering(state))
+        return signal
+
+    def after_leaving(self, state):
+        signal = self._get_signal(self.after_leaving_signals, state)
+        self.sync += signal.eq(self.before_leaving(state))
+        return signal
+
+    def do_finalize(self):
+        nstates = len(self.actions)
+        self.encoding = dict((s, n) for n, s in enumerate(self.actions.keys()))
+        self.state = Signal(max=nstates, reset=self.encoding[self.reset_state])
+        self.next_state = Signal(max=nstates)
+
+        ln = _LowerNext(self.next_state, self.encoding, self.state_aliases)
+        cases = dict((self.encoding[k], ln.visit(v)) for k, v in self.actions.items() if v)
+        self.comb += [
+            self.next_state.eq(self.state),
+            Case(self.state, cases).makedefault(self.encoding[self.reset_state])
+        ]
+        self.sync += self.state.eq(self.next_state)
+        for register, next_value_ce, next_value in ln.registers:
+            self.sync += If(next_value_ce, register.eq(next_value))
+
+        # drive entering/leaving signals
+        for state, signal in self.before_leaving_signals.items():
+            encoded = self.encoding[state]
+            self.comb += signal.eq((self.state == encoded) & ~(self.next_state == encoded))
+        if self.reset_state in self.after_entering_signals:
+            self.after_entering_signals[self.reset_state].reset = 1
+        for state, signal in self.before_entering_signals.items():
+            encoded = self.encoding[state]
+            self.comb += signal.eq(~(self.state == encoded) & (self.next_state == encoded))
diff --git a/litex/gen/genlib/io.py b/litex/gen/genlib/io.py
new file mode 100644 (file)
index 0000000..5c441e6
--- /dev/null
@@ -0,0 +1,96 @@
+from litex.gen.fhdl.structure import *
+from litex.gen.fhdl.module import Module
+from litex.gen.fhdl.specials import Special
+
+
+class DifferentialInput(Special):
+    def __init__(self, i_p, i_n, o):
+        Special.__init__(self)
+        self.i_p = wrap(i_p)
+        self.i_n = wrap(i_n)
+        self.o = wrap(o)
+
+    def iter_expressions(self):
+        yield self, "i_p", SPECIAL_INPUT
+        yield self, "i_n", SPECIAL_INPUT
+        yield self, "o", SPECIAL_OUTPUT
+
+    @staticmethod
+    def lower(dr):
+        raise NotImplementedError("Attempted to use a differential input, but platform does not support them")
+
+
+class DifferentialOutput(Special):
+    def __init__(self, i, o_p, o_n):
+        Special.__init__(self)
+        self.i = wrap(i)
+        self.o_p = wrap(o_p)
+        self.o_n = wrap(o_n)
+
+    def iter_expressions(self):
+        yield self, "i", SPECIAL_INPUT
+        yield self, "o_p", SPECIAL_OUTPUT
+        yield self, "o_n", SPECIAL_OUTPUT
+
+    @staticmethod
+    def lower(dr):
+        raise NotImplementedError("Attempted to use a differential output, but platform does not support them")
+
+
+class CRG(Module):
+    def __init__(self, clk, rst=0):
+        self.clock_domains.cd_sys = ClockDomain()
+        self.clock_domains.cd_por = ClockDomain(reset_less=True)
+
+        if hasattr(clk, "p"):
+            clk_se = Signal()
+            self.specials += DifferentialInput(clk.p, clk.n, clk_se)
+            clk = clk_se
+
+        # Power on Reset (vendor agnostic)
+        int_rst = Signal(reset=1)
+        self.sync.por += int_rst.eq(rst)
+        self.comb += [
+            self.cd_sys.clk.eq(clk),
+            self.cd_por.clk.eq(clk),
+            self.cd_sys.rst.eq(int_rst)
+        ]
+
+
+class DDRInput(Special):
+    def __init__(self, i, o1, o2, clk=ClockSignal()):
+        Special.__init__(self)
+        self.i = wrap(i)
+        self.o1 = wrap(o1)
+        self.o2 = wrap(o2)
+        self.clk = wrap(clk)
+
+    def iter_expressions(self):
+        yield self, "i", SPECIAL_INPUT
+        yield self, "o1", SPECIAL_OUTPUT
+        yield self, "o2", SPECIAL_OUTPUT
+        yield self, "clk", SPECIAL_INPUT
+
+    @staticmethod
+    def lower(dr):
+        raise NotImplementedError("Attempted to use a DDR input, but platform does not support them")
+
+
+class DDROutput(Special):
+    def __init__(self, i1, i2, o, clk=ClockSignal()):
+        Special.__init__(self)
+        self.i1 = i1
+        self.i2 = i2
+        self.o = o
+        self.clk = clk
+
+    def iter_expressions(self):
+        yield self, "i1", SPECIAL_INPUT
+        yield self, "i2", SPECIAL_INPUT
+        yield self, "o", SPECIAL_OUTPUT
+        yield self, "clk", SPECIAL_INPUT
+
+    @staticmethod
+    def lower(dr):
+        raise NotImplementedError("Attempted to use a DDR output, but platform does not support them")
+
diff --git a/litex/gen/genlib/misc.py b/litex/gen/genlib/misc.py
new file mode 100644 (file)
index 0000000..42ad878
--- /dev/null
@@ -0,0 +1,88 @@
+from litex.gen.fhdl.structure import *
+from litex.gen.fhdl.module import Module
+from litex.gen.fhdl.bitcontainer import bits_for
+
+
+def split(v, *counts):
+    r = []
+    offset = 0
+    for n in counts:
+        if n != 0:
+            r.append(v[offset:offset+n])
+        else:
+            r.append(None)
+        offset += n
+    return tuple(r)
+
+
+def displacer(signal, shift, output, n=None, reverse=False):
+    if shift is None:
+        return output.eq(signal)
+    if n is None:
+        n = 2**len(shift)
+    w = len(signal)
+    if reverse:
+        r = reversed(range(n))
+    else:
+        r = range(n)
+    l = [Replicate(shift == i, w) & signal for i in r]
+    return output.eq(Cat(*l))
+
+
+def chooser(signal, shift, output, n=None, reverse=False):
+    if shift is None:
+        return output.eq(signal)
+    if n is None:
+        n = 2**len(shift)
+    w = len(output)
+    cases = {}
+    for i in range(n):
+        if reverse:
+            s = n - i - 1
+        else:
+            s = i
+        cases[i] = [output.eq(signal[s*w:(s+1)*w])]
+    return Case(shift, cases).makedefault()
+
+
+def timeline(trigger, events):
+    lastevent = max([e[0] for e in events])
+    counter = Signal(max=lastevent+1)
+
+    counterlogic = If(counter != 0,
+        counter.eq(counter + 1)
+    ).Elif(trigger,
+        counter.eq(1)
+    )
+    # insert counter reset if it doesn't naturally overflow
+    # (test if lastevent+1 is a power of 2)
+    if (lastevent & (lastevent + 1)) != 0:
+        counterlogic = If(counter == lastevent,
+            counter.eq(0)
+        ).Else(
+            counterlogic
+        )
+
+    def get_cond(e):
+        if e[0] == 0:
+            return trigger & (counter == 0)
+        else:
+            return counter == e[0]
+    sync = [If(get_cond(e), *e[1]) for e in events]
+    sync.append(counterlogic)
+    return sync
+
+
+class WaitTimer(Module):
+    def __init__(self, t):
+        self.wait = Signal()
+        self.done = Signal()
+
+        # # #
+
+        count = Signal(bits_for(t), reset=t)
+        self.comb += self.done.eq(count == 0)
+        self.sync += \
+            If(self.wait,
+                If(~self.done, count.eq(count - 1))
+            ).Else(count.eq(count.reset))
diff --git a/litex/gen/genlib/record.py b/litex/gen/genlib/record.py
new file mode 100644 (file)
index 0000000..0038b06
--- /dev/null
@@ -0,0 +1,179 @@
+from litex.gen.fhdl.structure import *
+from litex.gen.fhdl.tracer import get_obj_var_name
+
+from functools import reduce
+from operator import or_
+
+
+(DIR_NONE, DIR_S_TO_M, DIR_M_TO_S) = range(3)
+
+# Possible layout elements:
+#   1. (name, size)
+#   2. (name, size, direction)
+#   3. (name, sublayout)
+# size can be an int, or a (int, bool) tuple for signed numbers
+# sublayout must be a list
+
+
+def set_layout_parameters(layout, **layout_dict):
+    def resolve(p):
+        if isinstance(p, str):
+            try:
+                return layout_dict[p]
+            except KeyError:
+                return p
+        else:
+            return p
+
+    r = []
+    for f in layout:
+        if isinstance(f[1], (int, tuple, str)):  # cases 1/2
+            if len(f) == 3:
+                r.append((f[0], resolve(f[1]), f[2]))
+            else:
+                r.append((f[0], resolve(f[1])))
+        elif isinstance(f[1], list):  # case 3
+            r.append((f[0], set_layout_parameters(f[1], **layout_dict)))
+        else:
+            raise TypeError
+    return r
+
+
+def layout_len(layout):
+    r = 0
+    for f in layout:
+        if isinstance(f[1], (int, tuple)):  # cases 1/2
+            if len(f) == 3:
+                fname, fsize, fdirection = f
+            else:
+                fname, fsize = f
+        elif isinstance(f[1], list):  # case 3
+            fname, fsublayout = f
+            fsize = layout_len(fsublayout)
+        else:
+            raise TypeError
+        if isinstance(fsize, tuple):
+            r += fsize[0]
+        else:
+            r += fsize
+    return r
+
+
+def layout_get(layout, name):
+    for f in layout:
+        if f[0] == name:
+            return f
+    raise KeyError(name)
+
+
+def layout_partial(layout, *elements):
+    r = []
+    for path in elements:
+        path_s = path.split("/")
+        last = path_s.pop()
+        copy_ref = layout
+        insert_ref = r
+        for hop in path_s:
+            name, copy_ref = layout_get(copy_ref, hop)
+            try:
+                name, insert_ref = layout_get(insert_ref, hop)
+            except KeyError:
+                new_insert_ref = []
+                insert_ref.append((hop, new_insert_ref))
+                insert_ref = new_insert_ref
+        insert_ref.append(layout_get(copy_ref, last))
+    return r
+
+
+class Record:
+    def __init__(self, layout, name=None):
+        self.name = get_obj_var_name(name, "")
+        self.layout = layout
+
+        if self.name:
+            prefix = self.name + "_"
+        else:
+            prefix = ""
+        for f in self.layout:
+            if isinstance(f[1], (int, tuple)):  # cases 1/2
+                if(len(f) == 3):
+                    fname, fsize, fdirection = f
+                else:
+                    fname, fsize = f
+                finst = Signal(fsize, name=prefix + fname)
+            elif isinstance(f[1], list):  # case 3
+                fname, fsublayout = f
+                finst = Record(fsublayout, prefix + fname)
+            else:
+                raise TypeError
+            setattr(self, fname, finst)
+
+    def eq(self, other):
+        return [getattr(self, f[0]).eq(getattr(other, f[0]))
+          for f in self.layout if hasattr(other, f[0])]
+
+    def iter_flat(self):
+        for f in self.layout:
+            e = getattr(self, f[0])
+            if isinstance(e, Signal):
+                if len(f) == 3:
+                    yield e, f[2]
+                else:
+                    yield e, DIR_NONE
+            elif isinstance(e, Record):
+                yield from e.iter_flat()
+            else:
+                raise TypeError
+
+    def flatten(self):
+        return [signal for signal, direction in self.iter_flat()]
+
+    def raw_bits(self):
+        return Cat(*self.flatten())
+
+    def connect(self, *slaves, leave_out=set()):
+        if isinstance(leave_out, str):
+            leave_out = {leave_out}
+        r = []
+        for f in self.layout:
+            field = f[0]
+            if field not in leave_out:
+                self_e = getattr(self, field)
+                if isinstance(self_e, Signal):
+                    direction = f[2]
+                    if direction == DIR_M_TO_S:
+                        r += [getattr(slave, field).eq(self_e) for slave in slaves]
+                    elif direction == DIR_S_TO_M:
+                        r.append(self_e.eq(reduce(or_, [getattr(slave, field) for slave in slaves])))
+                    else:
+                        raise TypeError
+                else:
+                    for slave in slaves:
+                        r += self_e.connect(getattr(slave, field), leave_out=leave_out)
+        return r
+
+    def connect_flat(self, *slaves):
+        r = []
+        iter_slaves = [slave.iter_flat() for slave in slaves]
+        for m_signal, m_direction in self.iter_flat():
+            if m_direction == DIR_M_TO_S:
+                for iter_slave in iter_slaves:
+                    s_signal, s_direction = next(iter_slave)
+                    assert(s_direction == DIR_M_TO_S)
+                    r.append(s_signal.eq(m_signal))
+            elif m_direction == DIR_S_TO_M:
+                s_signals = []
+                for iter_slave in iter_slaves:
+                    s_signal, s_direction = next(iter_slave)
+                    assert(s_direction == DIR_S_TO_M)
+                    s_signals.append(s_signal)
+                r.append(m_signal.eq(reduce(or_, s_signals)))
+            else:
+                raise TypeError
+        return r
+
+    def __len__(self):
+        return layout_len(self.layout)
+
+    def __repr__(self):
+        return "<Record " + ":".join(f[0] for f in self.layout) + " at " + hex(id(self)) + ">"
diff --git a/litex/gen/genlib/resetsync.py b/litex/gen/genlib/resetsync.py
new file mode 100644 (file)
index 0000000..061f65b
--- /dev/null
@@ -0,0 +1,18 @@
+from litex.gen.fhdl.structure import *
+from litex.gen.fhdl.specials import Special
+
+
+class AsyncResetSynchronizer(Special):
+    def __init__(self, cd, async_reset):
+        Special.__init__(self)
+        self.cd = cd
+        self.async_reset = wrap(async_reset)
+
+    def iter_expressions(self):
+        yield self.cd, "clk", SPECIAL_INPUT
+        yield self.cd, "rst", SPECIAL_OUTPUT
+        yield self, "async_reset", SPECIAL_INPUT
+
+    @staticmethod
+    def lower(dr):
+        raise NotImplementedError("Attempted to use a reset synchronizer, but platform does not support them")
diff --git a/litex/gen/genlib/roundrobin.py b/litex/gen/genlib/roundrobin.py
new file mode 100644 (file)
index 0000000..b9903db
--- /dev/null
@@ -0,0 +1,41 @@
+from litex.gen.fhdl.structure import *
+from litex.gen.fhdl.module import Module
+
+
+(SP_WITHDRAW, SP_CE) = range(2)
+
+
+class RoundRobin(Module):
+    def __init__(self, n, switch_policy=SP_WITHDRAW):
+        self.request = Signal(n)
+        self.grant = Signal(max=max(2, n))
+        self.switch_policy = switch_policy
+        if self.switch_policy == SP_CE:
+            self.ce = Signal()
+
+        ###
+
+        if n > 1:
+            cases = {}
+            for i in range(n):
+                switch = []
+                for j in reversed(range(i+1, i+n)):
+                    t = j % n
+                    switch = [
+                        If(self.request[t],
+                            self.grant.eq(t)
+                        ).Else(
+                            *switch
+                        )
+                    ]
+                if self.switch_policy == SP_WITHDRAW:
+                    case = [If(~self.request[i], *switch)]
+                else:
+                    case = switch
+                cases[i] = case
+            statement = Case(self.grant, cases)
+            if self.switch_policy == SP_CE:
+                statement = If(self.ce, statement)
+            self.sync += statement
+        else:
+            self.comb += self.grant.eq(0)
diff --git a/litex/gen/genlib/sort.py b/litex/gen/genlib/sort.py
new file mode 100644 (file)
index 0000000..4c1f46c
--- /dev/null
@@ -0,0 +1,71 @@
+from litex.gen.fhdl.structure import *
+from litex.gen.fhdl.module import Module
+
+
+class BitonicSort(Module):
+    """Combinatorial sorting network
+
+    The Bitonic sort is implemented as a combinatorial sort using
+    comparators and multiplexers. Its asymptotic complexity (in terms of
+    number of comparators/muxes) is O(n log(n)**2), like mergesort or
+    shellsort.
+
+    http://www.dps.uibk.ac.at/~cosenza/teaching/gpu/sort-batcher.pdf
+
+    http://www.inf.fh-lensburg.de/lang/algorithmen/sortieren/bitonic/bitonicen.htm
+
+    http://www.myhdl.org/doku.php/cookbook:bitonic
+
+    Parameters
+    ----------
+    n : int
+        Number of inputs and output signals.
+    m : int
+        Bit width of inputs and outputs. Or a tuple of `(m, signed)`.
+    ascending : bool
+        Sort direction. `True` if input is to be sorted ascending,
+        `False` for descending. Defaults to ascending.
+
+    Attributes
+    ----------
+    i : list of Signals, in
+        Input values, each `m` wide.
+    o : list of Signals, out
+        Output values, sorted, each `m` bits wide.
+    """
+    def __init__(self, n, m, ascending=True):
+        self.i = [Signal(m) for i in range(n)]
+        self.o = [Signal(m) for i in range(n)]
+        self._sort(self.i, self.o, int(ascending), m)
+
+    def _sort_two(self, i0, i1, o0, o1, dir):
+        self.comb += [
+                o0.eq(i0),
+                o1.eq(i1),
+                If(dir == (i0 > i1),
+                    o0.eq(i1),
+                    o1.eq(i0),
+                )]
+
+    def _merge(self, i, o, dir, m):
+        n = len(i)
+        k = n//2
+        if n > 1:
+            t = [Signal(m) for j in range(n)]
+            for j in range(k):
+                self._sort_two(i[j], i[j + k], t[j], t[j + k], dir)
+            self._merge(t[:k], o[:k], dir, m)
+            self._merge(t[k:], o[k:], dir, m)
+        else:
+            self.comb += o[0].eq(i[0])
+
+    def _sort(self, i, o, dir, m):
+        n = len(i)
+        k = n//2
+        if n > 1:
+            t = [Signal(m) for j in range(n)]
+            self._sort(i[:k], t[:k], 1, m)  # ascending
+            self._sort(i[k:], t[k:], 0, m)  # descending
+            self._merge(t, o, dir, m)
+        else:
+            self.comb += o[0].eq(i[0])
diff --git a/litex/gen/sim/__init__.py b/litex/gen/sim/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/litex/gen/sim/generic.py b/litex/gen/sim/generic.py
new file mode 100644 (file)
index 0000000..c0ed3c5
--- /dev/null
@@ -0,0 +1,230 @@
+import warnings
+import sys
+
+from litex.gen import *
+from litex.gen.fhdl.structure import _Fragment
+
+from litex.gen.fhdl import verilog
+from litex.gen.sim.ipc import *
+from litex.gen.sim import icarus
+
+
+class TopLevel:
+    def __init__(self, vcd_name=None, vcd_level=1,
+      top_name="top", dut_type="dut", dut_name="dut",
+      cd_name="sys", clk_period=10):
+        self.vcd_name = vcd_name
+        self.vcd_level = vcd_level
+        self.top_name = top_name
+        self.dut_type = dut_type
+        self.dut_name = dut_name
+
+        self._cd_name = cd_name
+        self._clk_period = clk_period
+
+        cd = ClockDomain(self._cd_name)
+        self.clock_domains = [cd]
+        self.ios = {cd.clk, cd.rst}
+
+    def get(self, sockaddr):
+        if sys.platform == "win32":
+            sockaddr = sockaddr[0]  # Get the IP address only
+
+        template1 = """`timescale 1ns / 1ps
+
+module {top_name}();
+
+reg {clk_name};
+reg {rst_name};
+
+initial begin
+    {rst_name} <= 1'b1;
+    @(posedge {clk_name});
+    {rst_name} <= 1'b0;
+end
+
+always begin
+    {clk_name} <= 1'b0;
+    #{hclk_period};
+    {clk_name} <= 1'b1;
+    #{hclk_period};
+end
+
+{dut_type} {dut_name}(
+    .{rst_name}({rst_name}),
+    .{clk_name}({clk_name})
+);
+
+initial $migensim_connect("{sockaddr}");
+always @(posedge {clk_name}) $migensim_tick;
+"""
+        template2 = """
+initial begin
+    $dumpfile("{vcd_name}");
+    $dumpvars({vcd_level}, {dut_name});
+end
+"""
+        r = template1.format(top_name=self.top_name,
+            dut_type=self.dut_type,
+            dut_name=self.dut_name,
+            clk_name=self._cd_name + "_clk",
+            rst_name=self._cd_name + "_rst",
+            hclk_period=str(self._clk_period/2),
+            sockaddr=sockaddr)
+        if self.vcd_name is not None:
+            r += template2.format(vcd_name=self.vcd_name,
+                vcd_level=str(self.vcd_level),
+                dut_name=self.dut_name)
+        r += "\nendmodule"
+        return r
+
+
+class Simulator:
+    def __init__(self, fragment, top_level=None, sim_runner=None, sockaddr="simsocket", **vopts):
+        if not isinstance(fragment, _Fragment):
+            fragment = fragment.get_fragment()
+        if top_level is None:
+            top_level = TopLevel()
+        if sim_runner is None:
+            sim_runner = icarus.Runner()
+        self.top_level = top_level
+        if sys.platform == "win32":
+            sockaddr = ("127.0.0.1", 50007)
+            self.ipc = Initiator(sockaddr)
+        else:
+            self.ipc = Initiator(sockaddr)
+
+        self.sim_runner = sim_runner
+
+        c_top = self.top_level.get(sockaddr)
+
+        fragment = fragment + _Fragment(clock_domains=top_level.clock_domains)
+        c_fragment = verilog.convert(fragment,
+            ios=self.top_level.ios,
+            name=self.top_level.dut_type,
+            **vopts)
+        self.namespace = c_fragment.ns
+
+        self.cycle_counter = -1
+
+        self.sim_runner = sim_runner
+        self.sim_runner.start(c_top, c_fragment)
+        self.ipc.accept()
+        reply = self.ipc.recv()
+        assert(isinstance(reply, MessageTick))
+
+        self.sim_functions = fragment.sim
+        self.active_sim_functions = set(f for f in fragment.sim if not hasattr(f, "passive") or not f.passive)
+        self.unreferenced = {}
+
+    def run(self, ncycles=None):
+        counter = 0
+
+        if self.active_sim_functions:
+            if ncycles is None:
+                def continue_simulation():
+                    return bool(self.active_sim_functions)
+            else:
+                def continue_simulation():
+                    return self.active_sim_functions and counter < ncycles
+        else:
+            if ncycles is None:
+                raise ValueError("No active simulation function present - must specify ncycles to end simulation")
+            def continue_simulation():
+                return counter < ncycles
+
+        while continue_simulation():
+            self.cycle_counter += 1
+            counter += 1
+            self.ipc.send(MessageGo())
+            reply = self.ipc.recv()
+            assert(isinstance(reply, MessageTick))
+
+            del_list = []
+            for s in self.sim_functions:
+                try:
+                    s(self)
+                except StopSimulation:
+                    del_list.append(s)
+            for s in del_list:
+                self.sim_functions.remove(s)
+                try:
+                    self.active_sim_functions.remove(s)
+                except KeyError:
+                    pass
+
+    def get_unreferenced(self, item, index):
+        try:
+            return self.unreferenced[(item, index)]
+        except KeyError:
+            if isinstance(item, Memory):
+                try:
+                    init = item.init[index]
+                except (TypeError, IndexError):
+                    init = 0
+            else:
+                init = item.reset
+            self.unreferenced[(item, index)] = init
+            return init
+
+    def rd(self, item, index=0):
+        try:
+            name = self.top_level.top_name + "." \
+              + self.top_level.dut_name + "." \
+              + self.namespace.get_name(item)
+            self.ipc.send(MessageRead(name, Int32(index)))
+            reply = self.ipc.recv()
+            assert(isinstance(reply, MessageReadReply))
+            value = reply.value
+        except KeyError:
+            value = self.get_unreferenced(item, index)
+        if isinstance(item, Memory):
+            signed = False
+            nbits = item.width
+        else:
+            signed = item.signed
+            nbits = len(item)
+        value = value & (2**nbits - 1)
+        if signed and (value & 2**(nbits - 1)):
+            value -= 2**nbits
+        return value
+
+    def wr(self, item, value, index=0):
+        if isinstance(item, Memory):
+            nbits = item.width
+        else:
+            nbits = len(item)
+        if value < 0:
+            value += 2**nbits
+        assert(value >= 0 and value < 2**nbits)
+        try:
+            name = self.top_level.top_name + "." \
+              + self.top_level.dut_name + "." \
+              + self.namespace.get_name(item)
+            self.ipc.send(MessageWrite(name, Int32(index), value))
+        except KeyError:
+            self.unreferenced[(item, index)] = value
+
+    def __del__(self):
+        if hasattr(self, "ipc"):
+            warnings.warn("call Simulator.close() to clean up "
+                    "or use it as a contextmanager", DeprecationWarning)
+            self.close()
+
+    def close(self):
+        self.ipc.close()
+        self.sim_runner.close()
+        del self.ipc
+        del self.sim_runner
+
+    def __enter__(self):
+        return self
+
+    def __exit__(self, type, value, traceback):
+        self.close()
+
+
+def run_simulation(fragment, ncycles=None, vcd_name=None, **kwargs):
+    with Simulator(fragment, TopLevel(vcd_name), icarus.Runner(**kwargs)) as s:
+        s.run(ncycles)
+
diff --git a/litex/gen/sim/icarus.py b/litex/gen/sim/icarus.py
new file mode 100644 (file)
index 0000000..59dc2b7
--- /dev/null
@@ -0,0 +1,43 @@
+# Copyright (C) 2012 Vermeer Manufacturing Co.
+# License: GPLv3 with additional permissions (see README).
+
+import subprocess
+import os
+import time
+
+
+class Runner:
+    def __init__(self, options=None, extra_files=None, top_file="migensim_top.v", dut_file="migensim_dut.v", vvp_file=None, keep_files=False):
+        if extra_files is None: extra_files = []
+        if vvp_file is None: vvp_file = dut_file + "vp"
+        if options is None: options = []
+        self.options = options
+        self.extra_files = extra_files
+        self.top_file = top_file
+        self.dut_file = dut_file
+        self.vvp_file = vvp_file
+        self.data_files = []
+        self.keep_files = keep_files
+
+    def start(self, c_top, c_dut):
+        with open(self.top_file, "w") as f:
+            f.write(c_top)
+        c_dut.write(self.dut_file)
+        self.data_files += c_dut.data_files.keys()
+        subprocess.check_call(["iverilog", "-o", self.vvp_file] + self.options + [self.top_file, self.dut_file] + self.extra_files)
+        self.process = subprocess.Popen(["vvp", "-mmigensim", "-Mvpi", self.vvp_file])
+
+    def close(self):
+        if hasattr(self, "process"):
+            self.process.terminate()
+            if self.process.poll() is None:
+                time.sleep(.1)
+                self.process.kill()
+            self.process.wait()
+        if not self.keep_files:
+            for f in [self.top_file, self.dut_file, self.vvp_file] + self.data_files:
+                try:
+                    os.remove(f)
+                except OSError:
+                    pass
+        self.data_files.clear()
diff --git a/litex/gen/sim/ipc.py b/litex/gen/sim/ipc.py
new file mode 100644 (file)
index 0000000..c6124bb
--- /dev/null
@@ -0,0 +1,228 @@
+# Copyright (C) 2012 Vermeer Manufacturing Co.
+# License: GPLv3 with additional permissions (see README).
+
+import socket
+import os
+import sys
+import struct
+
+if sys.platform == "win32":
+    header_len = 2
+
+#
+# Message classes
+#
+
+class Int32(int):
+    pass
+
+
+class Message:
+    def __init__(self, *pvalues):
+        for parameter, value in zip(self.parameters, pvalues):
+            setattr(self, parameter[1], parameter[0](value))
+
+    def __str__(self):
+        p = []
+        for parameter in self.parameters:
+            p.append(parameter[1] + "=" + str(getattr(self, parameter[1])))
+        if p:
+            pf = " " + " ".join(p)
+        else:
+            pf = ""
+        return "<" + self.__class__.__name__ + pf + ">"
+
+
+class MessageTick(Message):
+    code = 0
+    parameters = []
+
+
+class MessageGo(Message):
+    code = 1
+    parameters = []
+
+
+class MessageWrite(Message):
+    code = 2
+    parameters = [(str, "name"), (Int32, "index"), (int, "value")]
+
+
+class MessageRead(Message):
+    code = 3
+    parameters = [(str, "name"), (Int32, "index")]
+
+
+class MessageReadReply(Message):
+    code = 4
+    parameters = [(int, "value")]
+
+message_classes = [MessageTick, MessageGo, MessageWrite, MessageRead, MessageReadReply]
+
+
+#
+# Packing
+#
+
+def _pack_int(v):
+    if v == 0:
+        p = [1, 0]
+    else:
+        p = []
+        while v != 0:
+            p.append(v & 0xff)
+            v >>= 8
+        p.insert(0, len(p))
+    return p
+
+
+def _pack_str(v):
+    p = [ord(c) for c in v]
+    p.append(0)
+    return p
+
+
+def _pack_int16(v):
+    return [v & 0xff,
+            (v & 0xff00) >> 8]
+
+
+def _pack_int32(v):
+    return [
+        v & 0xff,
+        (v & 0xff00) >> 8,
+        (v & 0xff0000) >> 16,
+        (v & 0xff000000) >> 24
+    ]
+
+
+def _pack(message):
+    r = [message.code]
+    for t, p in message.parameters:
+        value = getattr(message, p)
+        assert(isinstance(value, t))
+        if t == int:
+            r += _pack_int(value)
+        elif t == str:
+            r += _pack_str(value)
+        elif t == Int32:
+            r += _pack_int32(value)
+        else:
+            raise TypeError
+    if sys.platform == "win32":
+        size = _pack_int16(len(r) + header_len)
+        r = size + r
+    return bytes(r)
+
+
+#
+# Unpacking
+#
+
+def _unpack_int(i, nchunks=None):
+    v = 0
+    power = 1
+    if nchunks is None:
+        nchunks = next(i)
+    for j in range(nchunks):
+        v += power*next(i)
+        power *= 256
+    return v
+
+
+def _unpack_str(i):
+    v = ""
+    c = next(i)
+    while c:
+        v += chr(c)
+        c = next(i)
+    return v
+
+
+def _unpack(message):
+    i = iter(message)
+    code = next(i)
+    msgclass = next(filter(lambda x: x.code == code, message_classes))
+    pvalues = []
+    for t, p in msgclass.parameters:
+        if t == int:
+            v = _unpack_int(i)
+        elif t == str:
+            v = _unpack_str(i)
+        elif t == Int32:
+            v = _unpack_int(i, 4)
+        else:
+            raise TypeError
+        pvalues.append(v)
+    return msgclass(*pvalues)
+
+
+#
+# I/O
+#
+
+class PacketTooLarge(Exception):
+    pass
+
+
+class Initiator:
+    def __init__(self, sockaddr):
+        self.sockaddr = sockaddr
+        if sys.platform == "win32":
+            self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+        else:
+            self.socket = socket.socket(socket.AF_UNIX, socket.SOCK_SEQPACKET)
+            self._cleanup_file()
+        self.socket.bind(self.sockaddr)
+        self.socket.listen(1)
+
+        self.ipc_rxbuffer = bytearray()
+
+    def _cleanup_file(self):
+        try:
+            os.remove(self.sockaddr)
+        except OSError:
+            pass
+
+    def accept(self):
+        self.conn, addr = self.socket.accept()
+
+    def send(self, message):
+        self.conn.send(_pack(message))
+
+    def recv_packet(self, maxlen):
+        if sys.platform == "win32":
+            while len(self.ipc_rxbuffer) < header_len:
+                self.ipc_rxbuffer += self.conn.recv(maxlen)
+            packet_len = struct.unpack("<H", self.ipc_rxbuffer[:header_len])[0]
+            while len(self.ipc_rxbuffer) < packet_len:
+                self.ipc_rxbuffer += self.conn.recv(maxlen)
+            packet = self.ipc_rxbuffer[header_len:packet_len]
+            self.ipc_rxbuffer = self.ipc_rxbuffer[packet_len:]
+        else:
+            packet = self.conn.recv(maxlen)
+        return packet
+
+    def recv(self):
+        maxlen = 2048
+        packet = self.recv_packet(maxlen)
+        if len(packet) < 1:
+            return None
+        if len(packet) >= maxlen:
+            raise PacketTooLarge
+        return _unpack(packet)
+
+    def close(self):
+        if hasattr(self, "conn"):
+            self.conn.shutdown(socket.SHUT_RDWR)
+            self.conn.close()
+        if hasattr(self, "socket"):
+            if sys.platform == "win32":
+                # don't shutdown our socket since closing connection
+                # seems to already have done it. (trigger an error
+                # otherwise)
+                self.socket.close()
+            else:
+                self.socket.shutdown(socket.SHUT_RDWR)
+                self.socket.close()
+                self._cleanup_file()
diff --git a/litex/gen/sim/upper.py b/litex/gen/sim/upper.py
new file mode 100644 (file)
index 0000000..0373377
--- /dev/null
@@ -0,0 +1,112 @@
+from litex.gen.fhdl.structure import Signal, StopSimulation
+from litex.gen.fhdl.specials import Memory
+
+
+class MemoryProxy:
+    def __init__(self, simulator, obj):
+        self.simulator = simulator
+        self._simproxy_obj = obj
+
+    def __getitem__(self, key):
+        if isinstance(key, int):
+            return self.simulator.rd(self._simproxy_obj, key)
+        else:
+            start, stop, step = key.indices(self._simproxy_obj.depth)
+            return [self.simulator.rd(self._simproxy_obj, i) for i in range(start, stop, step)]
+
+    def __setitem__(self, key, value):
+        if isinstance(key, int):
+            self.simulator.wr(self._simproxy_obj, key, value)
+        else:
+            start, stop, step = key.indices(self.__obj.depth)
+            if len(value) != (stop - start)//step:
+                raise ValueError
+            for i, v in zip(range(start, stop, step), value):
+                self.simulator.wr(self._simproxy_obj, i, v)
+
+
+class Proxy:
+    def __init__(self, simulator, obj):
+        object.__setattr__(self, "simulator", simulator)
+        object.__setattr__(self, "_simproxy_obj", obj)
+
+    def __process_get(self, item):
+        if isinstance(item, Signal):
+            return self.simulator.rd(item)
+        elif isinstance(item, Memory):
+            return MemoryProxy(self.simulator, item)
+        else:
+            return Proxy(self.simulator, item)
+
+    def __getattr__(self, name):
+        return self.__process_get(getattr(self._simproxy_obj, name))
+
+    def __setattr__(self, name, value):
+        item = getattr(self._simproxy_obj, name)
+        assert(isinstance(item, Signal))
+        self.simulator.wr(item, value)
+
+    def __getitem__(self, key):
+        return self.__process_get(self._simproxy_obj[key])
+
+    def __setitem__(self, key, value):
+        item = self._simproxy_obj[key]
+        assert(isinstance(item, Signal))
+        self.simulator.wr(item, value)
+
+
+def gen_sim(simg):
+    gens = dict()
+    resume_cycle = 0
+
+    def do_simulation(s):
+        nonlocal resume_cycle, gens
+
+        if isinstance(s, Proxy):
+            simulator = s.simulator
+        else:
+            simulator = s
+
+        if simulator.cycle_counter >= resume_cycle:
+            try:
+                gen = gens[simulator]
+            except KeyError:
+                gen = simg(s)
+                gens[simulator] = gen
+            try:
+                n = next(gen)
+            except StopIteration:
+                del gens[simulator]
+                raise StopSimulation
+            else:
+                if n is None:
+                    n = 1
+                resume_cycle = simulator.cycle_counter + n
+
+    if hasattr(simg, "passive"):
+        do_simulation.passive = simg.passive
+
+    return do_simulation
+
+
+def proxy_sim(target, simf):
+    proxies = dict()
+
+    def do_simulation(simulator):
+        nonlocal proxies
+
+        try:
+            proxy = proxies[simulator]
+        except KeyError:
+            proxy = Proxy(simulator, target)
+            proxies[simulator] = proxy
+        try:
+            simf(proxy)
+        except StopSimulation:
+            del proxies[simulator]
+            raise
+
+    if hasattr(simf, "passive"):
+        do_simulation.passive = simf.passive
+
+    return do_simulation
diff --git a/litex/gen/util/__init__.py b/litex/gen/util/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/litex/gen/util/misc.py b/litex/gen/util/misc.py
new file mode 100644 (file)
index 0000000..ff2240b
--- /dev/null
@@ -0,0 +1,29 @@
+from fractions import gcd
+import collections
+
+
+def flat_iteration(l):
+    for element in l:
+        if isinstance(element, collections.Iterable):
+            for element2 in flat_iteration(element):
+                yield element2
+        else:
+            yield element
+
+
+def xdir(obj, return_values=False):
+    for attr in dir(obj):
+        if attr[:2] != "__" and attr[-2:] != "__":
+            if return_values:
+                yield attr, getattr(obj, attr)
+            else:
+                yield attr
+
+
+def gcd_multiple(numbers):
+    l = len(numbers)
+    if l == 1:
+        return numbers[0]
+    else:
+        s = l//2
+        return gcd(gcd_multiple(numbers[:s]), gcd_multiple(numbers[s:]))
diff --git a/litex/gen/vpi/Makefile b/litex/gen/vpi/Makefile
new file mode 100644 (file)
index 0000000..5f4ed34
--- /dev/null
@@ -0,0 +1,27 @@
+INSTDIR = $(shell iverilog-vpi --install-dir)
+
+CFLAGS = -Wall -O2 $(CFLAGS_$@)
+VPI_CFLAGS := $(shell iverilog-vpi --cflags)
+# Define the below flags for a Windows build.
+# Make sure to run iverilog-vpi with -mingw and -ivl options if necessary!
+# i.e. iverilog-vpi -mingw=C:\msys64\mingw32 -ivl=C:\msys64\mingw32
+# MINGW_FLAGS=-lWs2_32
+
+OBJ=ipc.o main.o
+
+all: migensim.vpi
+
+%.o: %.c
+       $(CC) $(CFLAGS) $(VPI_CFLAGS) -c $(INCDIRS) -o $@ $<
+
+migensim.vpi: $(OBJ)
+       iverilog-vpi $(MINGW_FLAGS) --name=migensim $^
+
+install: migensim.vpi
+       install -m755 -t $(INSTDIR) $^
+
+clean:
+       rm -f $(OBJ)
+       rm -f migensim.vpi
+
+.PHONY: install clean
diff --git a/litex/gen/vpi/ipc.c b/litex/gen/vpi/ipc.c
new file mode 100644 (file)
index 0000000..a510041
--- /dev/null
@@ -0,0 +1,260 @@
+/*
+ * Copyright (C) 2012 Vermeer Manufacturing Co.
+ * License: GPLv3 with additional permissions (see README).
+ */
+
+#ifdef _WIN32
+#define WINVER 0x501
+#endif
+
+#include <assert.h>
+#include <sys/types.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+
+#ifdef _WIN32
+#include <winsock2.h>
+#include <ws2tcpip.h>
+#else
+#include <sys/socket.h>
+#include <sys/un.h>
+#endif
+
+
+#include "ipc.h"
+
+struct ipc_softc {
+       int socket;
+       go_handler h_go;
+       write_handler h_write;
+       read_handler h_read;
+       void *user;
+};
+
+#define MAX_LEN 2048
+
+#ifdef _WIN32
+#define HEADER_LEN 2
+#define SOCKET_PORT "50007"
+
+unsigned char ipc_rxbuffer[2*MAX_LEN];
+int ipc_rxlen;
+#else
+#define HEADER_LEN 0
+#endif
+
+struct ipc_softc *ipc_connect(const char *sockaddr,
+       go_handler h_go, write_handler h_write, read_handler h_read, void *user)
+{
+       struct ipc_softc *sc;
+#ifdef _WIN32
+       struct addrinfo hints, *my_addrinfo;
+       WSADATA wsaData;
+       ipc_rxlen = 0;
+#else
+       struct sockaddr_un addr;
+#endif
+
+       sc = malloc(sizeof(struct ipc_softc));
+       if(!sc) return NULL;
+
+       sc->h_go = h_go;
+       sc->h_write = h_write;
+       sc->h_read = h_read;
+       sc->user = user;
+
+#ifdef _WIN32
+       /* Initialize Winsock. */
+       if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) {
+               free(sc);
+               return NULL;
+       }
+
+       memset(&hints, 0, sizeof(hints));
+       hints.ai_family = AF_INET;
+       hints.ai_socktype = SOCK_STREAM;
+       hints.ai_protocol = IPPROTO_TCP;
+
+       if(getaddrinfo(sockaddr, SOCKET_PORT, NULL, &my_addrinfo) != 0) {
+               free(sc);
+               return NULL;
+       }
+
+       sc->socket = socket(AF_INET, SOCK_STREAM, 0);
+       if(sc->socket < 0) {
+               free(sc);
+               return NULL;
+       }
+
+       if(connect(sc->socket, my_addrinfo->ai_addr, my_addrinfo->ai_addrlen) != 0) {
+               close(sc->socket);
+               free(sc);
+               return NULL;
+       }
+#else
+       sc->socket = socket(AF_UNIX, SOCK_SEQPACKET, 0);
+       if(sc->socket < 0) {
+               free(sc);
+               return NULL;
+       }
+
+       addr.sun_family = AF_UNIX;
+       strcpy(addr.sun_path, sockaddr);
+       if(connect(sc->socket, (struct sockaddr *)&addr, sizeof(addr)) != 0) {
+               close(sc->socket);
+               free(sc);
+               return NULL;
+       }
+#endif
+
+       return sc;
+}
+
+void ipc_destroy(struct ipc_softc *sc)
+{
+       close(sc->socket);
+       free(sc);
+#ifdef _WIN32
+       WSACleanup();
+#endif
+}
+
+enum {
+       MESSAGE_TICK = 0,
+       MESSAGE_GO,
+       MESSAGE_WRITE,
+       MESSAGE_READ,
+       MESSAGE_READ_REPLY
+};
+
+static int ipc_receive_packet(struct ipc_softc *sc, unsigned char *buffer) {
+#ifdef _WIN32
+       int len;
+       int packet_len;
+       /* ensure we have packet header */
+       while(ipc_rxlen < HEADER_LEN) {
+               len = recv(sc->socket, (char *)&ipc_rxbuffer[ipc_rxlen], MAX_LEN, 0);
+               if(len)
+                       ipc_rxlen += len;
+       }
+
+       /* compute packet length and ensure we have the payload */
+       packet_len = (ipc_rxbuffer[1] << 8) | ipc_rxbuffer[0];
+       while(ipc_rxlen < packet_len) {
+               len = recv(sc->socket, (char *)&ipc_rxbuffer[ipc_rxlen], MAX_LEN, 0);
+               if(len)
+                       ipc_rxlen += len;
+       }
+
+       /* copy packet to buffer */
+       memcpy(buffer, ipc_rxbuffer + HEADER_LEN, packet_len - HEADER_LEN);
+
+       /* prepare ipc_rxbuffer for next packet */
+       ipc_rxlen = ipc_rxlen - packet_len;
+       memcpy(ipc_rxbuffer, ipc_rxbuffer + packet_len, ipc_rxlen);
+
+       return packet_len - HEADER_LEN;
+#else
+       return recv(sc->socket, buffer, MAX_LEN, 0);
+#endif
+}
+
+/*
+ * 0 -> error
+ * 1 -> success
+ * 2 -> graceful shutdown
+ */
+int ipc_receive(struct ipc_softc *sc)
+{
+       unsigned char buffer[MAX_LEN];
+       ssize_t l = 0;
+       int i;
+
+       l = ipc_receive_packet(sc, (unsigned char *)&buffer);
+       if(l == 0)
+               return 2;
+       if((l < 0) || (l >= MAX_LEN))
+               return 0;
+       i = 0;
+
+       switch(buffer[i++]) {
+               case MESSAGE_GO:
+                       assert((l - i) == 0);
+
+                       return sc->h_go(sc->user);
+               case MESSAGE_WRITE: {
+                       char *name;
+                       int nchunks;
+                       unsigned char *chunks;
+                       unsigned int chunk_index;
+
+                       name = (char *)&buffer[i];
+                       i += strlen(name) + 1;
+                       assert((i+4) < l);
+                       chunk_index = buffer[i] | buffer[i+1] << 8 | buffer[i+2] << 16 | buffer[i+3] << 24;
+                       i += 4;
+                       nchunks = buffer[i++];
+                       assert(i + nchunks == l);
+                       chunks = (unsigned char *)&buffer[i];
+
+                       return sc->h_write(name, chunk_index, nchunks, chunks, sc->user);
+               }
+               case MESSAGE_READ: {
+                       char *name;
+                       unsigned int name_index;
+
+                       name = (char *)&buffer[i];
+                       i += strlen(name) + 1;
+                       assert((i+4) == l);
+                       name_index = buffer[i] | buffer[i+1] << 8 | buffer[i+2] << 16 | buffer[i+3] << 24;
+
+                       return sc->h_read(name, name_index, sc->user);
+               }
+               default:
+                       return 0;
+       }
+}
+
+int ipc_tick(struct ipc_softc *sc)
+{
+       ssize_t l;
+       char c[HEADER_LEN + 1];
+
+#ifdef _WIN32
+       c[0] = 3;
+       c[1] = 0;
+#endif
+       c[HEADER_LEN + 0] = MESSAGE_TICK;
+       l = send(sc->socket, c, HEADER_LEN + 1, 0);
+       if(l != (HEADER_LEN + 1))
+               return 0;
+
+       return 1;
+}
+
+int ipc_read_reply(struct ipc_softc *sc, int nchunks, const unsigned char *chunks)
+{
+       int len;
+       char buffer[MAX_LEN];
+       ssize_t l;
+
+       len = nchunks + HEADER_LEN + 2;
+       assert(len < MAX_LEN);
+       assert(nchunks < 256);
+
+#ifdef _WIN32
+       buffer[0] = len & 0xFF;
+       buffer[1] = (0xFF00 & len) >> 8;
+#endif
+       buffer[HEADER_LEN + 0] = MESSAGE_READ_REPLY;
+       buffer[HEADER_LEN + 1] = nchunks;
+       memcpy(&buffer[HEADER_LEN + 2], chunks, nchunks);
+
+       l = send(sc->socket, buffer, len, 0);
+       if(l != len)
+               return 0;
+       return 1;
+}
+
diff --git a/litex/gen/vpi/ipc.h b/litex/gen/vpi/ipc.h
new file mode 100644 (file)
index 0000000..184858e
--- /dev/null
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2012 Vermeer Manufacturing Co.
+ * License: GPLv3 with additional permissions (see README).
+ */
+
+#ifndef __IPC_H
+#define __IPC_H
+
+struct ipc_softc;
+
+typedef int(*go_handler)(void *);
+typedef int(*write_handler)(char *, int, int, const unsigned char *, void *);
+typedef int(*read_handler)(char *, int, void *);
+
+struct ipc_softc *ipc_connect(const char *sockaddr, 
+       go_handler h_go, write_handler h_write, read_handler h_read, void *user);
+void ipc_destroy(struct ipc_softc *sc);
+
+int ipc_receive(struct ipc_softc *sc);
+
+int ipc_tick(struct ipc_softc *sc);
+int ipc_read_reply(struct ipc_softc *sc, int nchunks, const unsigned char *value);
+
+#endif /* __IPC_H */
diff --git a/litex/gen/vpi/main.c b/litex/gen/vpi/main.c
new file mode 100644 (file)
index 0000000..74c13fd
--- /dev/null
@@ -0,0 +1,203 @@
+/*
+ * Copyright (C) 2012 Vermeer Manufacturing Co.
+ * License: GPLv3 with additional permissions (see README).
+ */
+
+#include <assert.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <vpi_user.h>
+
+#include "ipc.h"
+
+struct migensim_softc {
+       struct ipc_softc *ipc;
+       int has_go;
+};
+
+static int h_go(void *user)
+{
+       struct migensim_softc *sc = (struct migensim_softc *)user;
+       sc->has_go = 1;
+       return 1;
+}
+
+static s_vpi_time zero_delay = {
+       .type = vpiSimTime,
+       .high = 0,
+       .low = 0
+};
+
+static int h_write(char *name, int index, int nchunks, const unsigned char *chunks, void *user)
+{
+       vpiHandle item;
+       s_vpi_vecval vector[64];
+       int i;
+       s_vpi_value value;
+       
+       item = vpi_handle_by_name(name, NULL);
+       if(item == NULL) {
+               fprintf(stderr, "Attempted to write non-existing signal %s\n", name);
+               return 0;
+       }
+       if(vpi_get(vpiType, item) == vpiMemory)
+               item = vpi_handle_by_index(item, index);
+       else
+               assert(index == 0);
+       
+       assert(nchunks <= 255);
+       for(i=0;i<64;i++) {
+               vector[i].aval = 0;
+               vector[i].bval = 0;
+       }
+       for(i=0;i<nchunks;i++)
+               vector[i/4].aval |= chunks[i] << 8*(i % 4);
+       
+       value.format = vpiVectorVal;
+       value.value.vector = vector;
+       vpi_put_value(item, &value, &zero_delay, vpiInertialDelay);
+       
+       return 1;
+}
+
+static int h_read(char *name, int index, void *user)
+{
+       struct migensim_softc *sc = (struct migensim_softc *)user;
+       vpiHandle item;
+       s_vpi_value value;
+       int size;
+       int i;
+       int nvals;
+       unsigned int vals[64];
+       int nchunks;
+       unsigned char chunks[255];
+       
+       item = vpi_handle_by_name(name, NULL);
+       if(item == NULL) {
+               fprintf(stderr, "Attempted to read non-existing signal %s\n", name);
+               return 0;
+       }
+       if(vpi_get(vpiType, item) == vpiMemory)
+               item = vpi_handle_by_index(item, index);
+       else
+               assert(index == 0);
+       
+       value.format = vpiVectorVal;
+       vpi_get_value(item, &value);
+       size = vpi_get(vpiSize, item);
+       nvals = (size + 31)/32;
+       assert(nvals <= 64);
+       for(i=0;i<nvals;i++)
+               vals[i] = value.value.vector[i].aval & ~value.value.vector[i].bval;
+       nchunks = (size + 7)/8;
+       assert(nchunks <= 255);
+       for(i=0;i<nchunks;i++) {
+               switch(i % 4) {
+                       case 0:
+                               chunks[i] = vals[i/4] & 0xff;
+                               break;
+                       case 1:
+                               chunks[i] = (vals[i/4] & 0xff00) >> 8;
+                               break;
+                       case 2:
+                               chunks[i] = (vals[i/4] & 0xff0000) >> 16;
+                               break;
+                       case 3:
+                               chunks[i] = (vals[i/4] & 0xff000000) >> 24;
+                               break;
+               }
+       }
+       
+       if(!ipc_read_reply(sc->ipc, nchunks, chunks)) {
+               perror("ipc_read_reply");
+               return 0;
+       }
+       
+       return 1;
+}
+
+static int process_until_go(struct migensim_softc *sc)
+{
+       int r;
+       
+       sc->has_go = 0;
+       while(!sc->has_go) {
+               r = ipc_receive(sc->ipc);
+               if(r != 1)
+                       return r;
+       }
+       return 1;
+}
+
+static PLI_INT32 connect_calltf(PLI_BYTE8 *user)
+{
+       struct migensim_softc *sc = (struct migensim_softc *)user;
+       vpiHandle sys;
+       vpiHandle argv;
+       vpiHandle item;
+       s_vpi_value value;
+       
+       sys = vpi_handle(vpiSysTfCall, 0);
+       argv = vpi_iterate(vpiArgument, sys);
+       item = vpi_scan(argv);
+       value.format = vpiStringVal;
+       vpi_get_value(item, &value);
+       
+       sc->ipc = ipc_connect(value.value.str, h_go, h_write, h_read, sc);
+       if(sc->ipc == NULL) {
+               perror("ipc_connect");
+               vpi_control(vpiFinish, 1);
+               return 0;
+       }
+       
+       return 0;
+}
+
+static PLI_INT32 tick_calltf(PLI_BYTE8 *user)
+{
+       struct migensim_softc *sc = (struct migensim_softc *)user;
+       int r;
+       
+       if(!ipc_tick(sc->ipc)) {
+               perror("ipc_tick");
+               vpi_control(vpiFinish, 1);
+               ipc_destroy(sc->ipc);
+               sc->ipc = NULL;
+               return 0;
+       }
+       r = process_until_go(sc);
+       if(r != 1) {
+               vpi_control(vpiFinish, r == 2 ? 0 : 1);
+               ipc_destroy(sc->ipc);
+               sc->ipc = NULL;
+               return 0;
+       }
+       
+       return 0;
+}
+
+static struct migensim_softc sc;
+
+static void simple_register(const char *tfname, PLI_INT32 (*calltf)(PLI_BYTE8 *))
+{
+       s_vpi_systf_data tf_data;
+
+       tf_data.type      = vpiSysTask;
+       tf_data.tfname    = tfname;
+       tf_data.calltf    = calltf;
+       tf_data.compiletf = NULL;
+       tf_data.sizetf    = 0;
+       tf_data.user_data = (void *)&sc;
+       vpi_register_systf(&tf_data);
+}
+
+static void migensim_register()
+{
+       simple_register("$migensim_connect", connect_calltf);
+       simple_register("$migensim_tick", tick_calltf);
+}
+
+void (*vlog_startup_routines[])() = {
+       migensim_register,
+       0
+};