From: Florent Kermarrec Date: Thu, 12 Nov 2015 19:06:59 +0000 (+0100) Subject: litex/gen: reintegrate migen with modifications to be able to simulate with vpi until... X-Git-Tag: 24jan2021_ls180~2071 X-Git-Url: https://git.libre-soc.org/?a=commitdiff_plain;h=ae3d54499aac40a08bfa7b2511733d3003391b42;p=litex.git litex/gen: reintegrate migen with modifications to be able to simulate with vpi until all missing features of the new simulator are implemented --- diff --git a/litex/gen/MIGEN_LICENSE b/litex/gen/MIGEN_LICENSE new file mode 100644 index 00000000..4f290601 --- /dev/null +++ b/litex/gen/MIGEN_LICENSE @@ -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. diff --git a/litex/gen/__init__.py b/litex/gen/__init__.py index e69de29b..3f70cb1f 100644 --- a/litex/gen/__init__.py +++ b/litex/gen/__init__.py @@ -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 index 00000000..e69de29b diff --git a/litex/gen/fhdl/bitcontainer.py b/litex/gen/fhdl/bitcontainer.py new file mode 100644 index 00000000..11a3ede7 --- /dev/null +++ b/litex/gen/fhdl/bitcontainer.py @@ -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 index 00000000..793fad20 --- /dev/null +++ b/litex/gen/fhdl/conv_output.py @@ -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 index 00000000..59444eaf --- /dev/null +++ b/litex/gen/fhdl/decorators.py @@ -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 index 00000000..f68c99bd --- /dev/null +++ b/litex/gen/fhdl/edif.py @@ -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 index 00000000..73848bba --- /dev/null +++ b/litex/gen/fhdl/module.py @@ -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 index 00000000..5e8ecdd3 --- /dev/null +++ b/litex/gen/fhdl/namer.py @@ -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 index 00000000..747f5628 --- /dev/null +++ b/litex/gen/fhdl/simplify.py @@ -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 index 00000000..d4fc2bf1 --- /dev/null +++ b/litex/gen/fhdl/specials.py @@ -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 index 00000000..113954bf --- /dev/null +++ b/litex/gen/fhdl/structure.py @@ -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 "" + + @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 index 00000000..3db91831 --- /dev/null +++ b/litex/gen/fhdl/tools.py @@ -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 index 00000000..a394f93f --- /dev/null +++ b/litex/gen/fhdl/tracer.py @@ -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 == "": + 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 index 00000000..a0fc3b16 --- /dev/null +++ b/litex/gen/fhdl/verilog.py @@ -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 index 00000000..0ccd5095 --- /dev/null +++ b/litex/gen/fhdl/visit.py @@ -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 index 00000000..e69de29b diff --git a/litex/gen/genlib/cdc.py b/litex/gen/genlib/cdc.py new file mode 100644 index 00000000..90b5f907 --- /dev/null +++ b/litex/gen/genlib/cdc.py @@ -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 index 00000000..80d62d6d --- /dev/null +++ b/litex/gen/genlib/coding.py @@ -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< 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 index 00000000..5c441e66 --- /dev/null +++ b/litex/gen/genlib/io.py @@ -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 index 00000000..42ad878c --- /dev/null +++ b/litex/gen/genlib/misc.py @@ -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 index 00000000..0038b069 --- /dev/null +++ b/litex/gen/genlib/record.py @@ -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 "" diff --git a/litex/gen/genlib/resetsync.py b/litex/gen/genlib/resetsync.py new file mode 100644 index 00000000..061f65bc --- /dev/null +++ b/litex/gen/genlib/resetsync.py @@ -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 index 00000000..b9903dbb --- /dev/null +++ b/litex/gen/genlib/roundrobin.py @@ -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 index 00000000..4c1f46c4 --- /dev/null +++ b/litex/gen/genlib/sort.py @@ -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 index 00000000..e69de29b diff --git a/litex/gen/sim/generic.py b/litex/gen/sim/generic.py new file mode 100644 index 00000000..c0ed3c54 --- /dev/null +++ b/litex/gen/sim/generic.py @@ -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 index 00000000..59dc2b7d --- /dev/null +++ b/litex/gen/sim/icarus.py @@ -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 index 00000000..c6124bb6 --- /dev/null +++ b/litex/gen/sim/ipc.py @@ -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("= 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 index 00000000..03733775 --- /dev/null +++ b/litex/gen/sim/upper.py @@ -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 index 00000000..e69de29b diff --git a/litex/gen/util/misc.py b/litex/gen/util/misc.py new file mode 100644 index 00000000..ff2240bc --- /dev/null +++ b/litex/gen/util/misc.py @@ -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 index 00000000..5f4ed341 --- /dev/null +++ b/litex/gen/vpi/Makefile @@ -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 index 00000000..a5100416 --- /dev/null +++ b/litex/gen/vpi/ipc.c @@ -0,0 +1,260 @@ +/* + * Copyright (C) 2012 Vermeer Manufacturing Co. + * License: GPLv3 with additional permissions (see README). + */ + +#ifdef _WIN32 +#define WINVER 0x501 +#endif + +#include +#include +#include +#include +#include +#include + +#ifdef _WIN32 +#include +#include +#else +#include +#include +#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 index 00000000..184858ee --- /dev/null +++ b/litex/gen/vpi/ipc.h @@ -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 index 00000000..74c13fd4 --- /dev/null +++ b/litex/gen/vpi/main.c @@ -0,0 +1,203 @@ +/* + * Copyright (C) 2012 Vermeer Manufacturing Co. + * License: GPLv3 with additional permissions (see README). + */ + +#include +#include +#include +#include + +#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> 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 *)≻ + 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 +};