Initial commit.
authorwhitequark <whitequark@whitequark.org>
Tue, 11 Dec 2018 20:50:56 +0000 (20:50 +0000)
committerwhitequark <whitequark@whitequark.org>
Wed, 12 Dec 2018 03:18:44 +0000 (03:18 +0000)
20 files changed:
.gitignore [new file with mode: 0644]
README.md [new file with mode: 0644]
examples/alu.py [new file with mode: 0644]
examples/alu_hier.py [new file with mode: 0644]
examples/arst.py [new file with mode: 0644]
examples/clkdiv.py [new file with mode: 0644]
examples/ctrl.py [new file with mode: 0644]
examples/pmux.py [new file with mode: 0644]
nmigen/back/__init__.py [new file with mode: 0644]
nmigen/back/rtlil.py [new file with mode: 0644]
nmigen/back/verilog.py [new file with mode: 0644]
nmigen/fhdl/__init__.py [new file with mode: 0644]
nmigen/fhdl/ast.py [new file with mode: 0644]
nmigen/fhdl/cd.py [new file with mode: 0644]
nmigen/fhdl/dsl.py [new file with mode: 0644]
nmigen/fhdl/ir.py [new file with mode: 0644]
nmigen/fhdl/xfrm.py [new file with mode: 0644]
nmigen/tools.py [new file with mode: 0644]
nmigen/tracer.py [new file with mode: 0644]
setup.py [new file with mode: 0644]

diff --git a/.gitignore b/.gitignore
new file mode 100644 (file)
index 0000000..29542a5
--- /dev/null
@@ -0,0 +1,4 @@
+*.pyc
+*.egg-info
+*.il
+*.v
diff --git a/README.md b/README.md
new file mode 100644 (file)
index 0000000..722c039
--- /dev/null
+++ b/README.md
@@ -0,0 +1,251 @@
+This repository contains a proposal for the design of nMigen in form of an implementation. This implementation deviates from the existing design of Migen by making several observations of its drawbacks:
+
+  * Migen is strongly tailored towards Verilog, yet translation of Migen to Verilog is not straightforward, leaves much semantics implicit (e.g. signedness, width extension, combinatorial assignments, sub-signal assignments...);
+  * Hierarchical designs are useful for floorplanning and optimization, yet Migen does not support them at all;
+  * Migen's syntax is not easily composable, and something like an FSM requires extending Migen's syntax in non-orthogonal ways;
+  * Migen reimplements a lot of mature open-source tooling, such as conversion of RTL to Verilog (Yosys' Verilog backend), or simulation (Icarus Verilog, Verilator, etc.), and often lacks in features, speed, or corner case handling.
+  * Migen requires awkward specials for some FPGA features such as asynchronous resets.
+
+It also observes that Yosys' intermediate language, RTLIL, is an ideal target for Migen-style logic, as conversion of FHDL to RTLIL is essentially a 1:1 translation, with the exception of the related issues of naming and hierarchy.
+
+This proposal makes several major changes to Migen that hopefully solve all of these drawbacks:
+
+  * nMigen changes FHDL's internal representation to closely match that of RTLIL;
+  * nMigen outputs RTLIL and relies on Yosys for conversion to Verilog, EDIF, etc;
+  * nMigen uses an exact mapping between FHDL signals and RTLIL names to off-load logic simulation to Icarus Verilog, Verilator, etc;
+  * nMigen uses an uniform, composable Python eHDL;
+  * nMigen outputs hierarchical RTLIL, automatically threading signals through the hierarchy;
+  * nMigen supports asynchronous reset directly;
+  * nMigen makes driving a signal from multiple clock domains a precise, hard error.
+
+This proposal keeps in mind but does not make the following major changes:
+
+  * nMigen could be easily modified to flatten the hierarchy if a signal is driven simultaneously from multiple modules;
+  * nMigen could be easily modified to support `x` values (invalid / don't care) by relying on RTLIL's ability to directly represent them;
+  * nMigen could be easily modified to support negative edge triggered flip-flops by relying on RTLIL's ability to directly represent them;
+  * nMigen could be easily modified to track Python source locations of primitives and export them to RTLIL/Verilog through the `src` attribute, displaying the Python source locations in timing reports directly.
+
+This proposal also makes the following simplifications:
+  * Specials are eliminated. Primitives such as memory ports are represented directly, and primitives such as tristate buffers are lowered to a selectable implementation via ordinary dependency injection (`f.submodules += platform.get_tristate(triple, io)`).
+
+The internals of nMigen in this proposal are cleaned up, yet they are kept sufficiently close to Migen that \~all Migen code should be possible to run directly on nMigen using a syntactic compatibility layer.
+
+FHDL features currently missing from this implementation:
+  * self.clock_domains +=
+  * Array
+  * Memory
+  * Tristate, TSTriple
+  * Instance
+  * FSM
+  * transformers: SplitMemory, FullMemoryWE
+  * transformers: ClockDomainsRenamer
+
+`migen.genlib`, `migen.sim` and `migen.build` are missing completely.
+
+One might reasonably expect that a roundtrip through RTLIL would result in unreadable Verilog.
+However, this is not the case, e.g. consider the examples:
+
+<details>
+<summary>alu.v</summary>
+
+```verilog
+module \$1 (co, sel, a, b, o);
+  wire [17:0] _04_;
+  input [15:0] a;
+  input [15:0] b;
+  output co;
+  reg \co$next ;
+  output [15:0] o;
+  reg [15:0] \o$next ;
+  input [1:0] sel;
+  assign _04_ = $signed(+ a) + $signed(- b);
+  always @* begin
+    \o$next  = 16'h0000;
+    \co$next  = 1'h0;
+    casez ({ 1'h1, sel == 2'h2, sel == 1'h1, sel == 0'b0 })
+      4'bzzz1:
+          \o$next  = a | b;
+      4'bzz1z:
+          \o$next  = a & b;
+      4'bz1zz:
+          \o$next  = a ^ b;
+      4'b1zzz:
+          { \co$next , \o$next  } = _04_[16:0];
+    endcase
+  end
+  assign o = \o$next ;
+  assign co = \co$next ;
+endmodule
+```
+</details>
+
+<details>
+<summary>alu_hier.v</summary>
+
+```verilog
+module add(b, o, a);
+  wire [16:0] _0_;
+  input [15:0] a;
+  input [15:0] b;
+  output [15:0] o;
+  reg [15:0] \o$next ;
+  assign _0_ = a + b;
+  always @* begin
+    \o$next  = 16'h0000;
+    \o$next  = _0_[15:0];
+  end
+  assign o = \o$next ;
+endmodule
+
+module sub(b, o, a);
+  wire [16:0] _0_;
+  input [15:0] a;
+  input [15:0] b;
+  output [15:0] o;
+  reg [15:0] \o$next ;
+  assign _0_ = a - b;
+  always @* begin
+    \o$next  = 16'h0000;
+    \o$next  = _0_[15:0];
+  end
+  assign o = \o$next ;
+endmodule
+
+module top(a, b, o, add_o, sub_o, op);
+  input [15:0] a;
+  wire [15:0] add_a;
+  reg [15:0] \add_a$next ;
+  wire [15:0] add_b;
+  reg [15:0] \add_b$next ;
+  input [15:0] add_o;
+  input [15:0] b;
+  output [15:0] o;
+  reg [15:0] \o$next ;
+  input op;
+  wire [15:0] sub_a;
+  reg [15:0] \sub_a$next ;
+  wire [15:0] sub_b;
+  reg [15:0] \sub_b$next ;
+  input [15:0] sub_o;
+  add add (
+    .a(add_a),
+    .b(add_b),
+    .o(add_o)
+  );
+  sub sub (
+    .a(sub_a),
+    .b(sub_b),
+    .o(sub_o)
+  );
+  always @* begin
+    \o$next  = 16'h0000;
+    \add_a$next  = 16'h0000;
+    \add_b$next  = 16'h0000;
+    \sub_a$next  = 16'h0000;
+    \sub_b$next  = 16'h0000;
+    \add_a$next  = a;
+    \sub_a$next  = a;
+    \add_b$next  = b;
+    \sub_b$next  = b;
+    casez ({ 1'h1, op })
+      2'bz1:
+          \o$next  = sub_o;
+      2'b1z:
+          \o$next  = add_o;
+    endcase
+  end
+  assign o = \o$next ;
+  assign add_a = \add_a$next ;
+  assign add_b = \add_b$next ;
+  assign sub_a = \sub_a$next ;
+  assign sub_b = \sub_b$next ;
+endmodule
+```
+</details>
+<details>
+<summary>clkdiv.v</summary>
+
+```verilog
+module \$1 (sys_clk, o);
+  wire [16:0] _0_;
+  output o;
+  reg \o$next ;
+  input sys_clk;
+  wire sys_rst;
+  (* init = 16'hffff *)
+  reg [15:0] v = 16'hffff;
+  reg [15:0] \v$next ;
+  assign _0_ = v + 1'h1;
+  always @(posedge sys_clk)
+      v <= \v$next ;
+  always @* begin
+    \o$next  = 1'h0;
+    \v$next  = _0_[15:0];
+    \o$next  = v[15];
+    casez (sys_rst)
+      1'h1:
+          \v$next  = 16'hffff;
+    endcase
+  end
+  assign o = \o$next ;
+endmodule
+```
+</details>
+
+<details>
+<summary>arst.v</summary>
+
+```verilog
+module \$1 (o, sys_clk, sys_rst);
+  wire [16:0] _0_;
+  output o;
+  reg \o$next ;
+  input sys_clk;
+  input sys_rst;
+  (* init = 16'h0000 *)
+  reg [15:0] v = 16'h0000;
+  reg [15:0] \v$next ;
+  assign _0_ = v + 1'h1;
+  always @(posedge sys_clk or posedge sys_rst)
+    if (sys_rst)
+      v <= 16'h0000;
+    else
+      v <= \v$next ;
+  always @* begin
+    \o$next  = 1'h0;
+    \v$next  = _0_[15:0];
+    \o$next  = v[15];
+  end
+  assign o = \o$next ;
+endmodule
+```
+</details>
+
+<details>
+<summary>pmux.v</summary>
+
+```verilog
+module \$1 (c, o, s, a, b);
+  input [15:0] a;
+  input [15:0] b;
+  input [15:0] c;
+  output [15:0] o;
+  reg [15:0] \o$next ;
+  input [2:0] s;
+  always @* begin
+    \o$next  = 16'h0000;
+    casez (s)
+      3'bzz1:
+          \o$next  = a;
+      3'bz1z:
+          \o$next  = b;
+      3'b1zz:
+          \o$next  = c;
+      3'hz:
+          \o$next  = 16'h0000;
+    endcase
+  end
+  assign o = \o$next ;
+endmodule
+```
+</details>
diff --git a/examples/alu.py b/examples/alu.py
new file mode 100644 (file)
index 0000000..1ffb176
--- /dev/null
@@ -0,0 +1,29 @@
+from nmigen.fhdl import *
+from nmigen.back import rtlil, verilog
+
+
+class ALU:
+    def __init__(self, width):
+        self.sel = Signal(2)
+        self.a   = Signal(width)
+        self.b   = Signal(width)
+        self.o   = Signal(width)
+        self.co  = Signal()
+
+    def get_fragment(self, platform):
+        f = Module()
+        with f.If(self.sel == 0b00):
+            f.comb += self.o.eq(self.a | self.b)
+        with f.Elif(self.sel == 0b01):
+            f.comb += self.o.eq(self.a & self.b)
+        with f.Elif(self.sel == 0b10):
+            f.comb += self.o.eq(self.a ^ self.b)
+        with f.Else():
+            f.comb += Cat(self.o, self.co).eq(self.a - self.b)
+        return f.lower(platform)
+
+
+alu  = ALU(width=16)
+frag = alu.get_fragment(platform=None)
+# print(rtlil.convert(frag, ports=[alu.sel, alu.a, alu.b, alu.o, alu.co]))
+print(verilog.convert(frag, ports=[alu.sel, alu.a, alu.b, alu.o, alu.co]))
diff --git a/examples/alu_hier.py b/examples/alu_hier.py
new file mode 100644 (file)
index 0000000..52d2c19
--- /dev/null
@@ -0,0 +1,59 @@
+from nmigen.fhdl import *
+from nmigen.back import rtlil, verilog
+
+
+class Adder:
+    def __init__(self, width):
+        self.a   = Signal(width)
+        self.b   = Signal(width)
+        self.o   = Signal(width)
+
+    def get_fragment(self, platform):
+        f = Module()
+        f.comb += self.o.eq(self.a + self.b)
+        return f.lower(platform)
+
+
+class Subtractor:
+    def __init__(self, width):
+        self.a   = Signal(width)
+        self.b   = Signal(width)
+        self.o   = Signal(width)
+
+    def get_fragment(self, platform):
+        f = Module()
+        f.comb += self.o.eq(self.a - self.b)
+        return f.lower(platform)
+
+
+class ALU:
+    def __init__(self, width):
+        self.op  = Signal()
+        self.a   = Signal(width)
+        self.b   = Signal(width)
+        self.o   = Signal(width)
+
+        self.add = Adder(width)
+        self.sub = Subtractor(width)
+
+    def get_fragment(self, platform):
+        f = Module()
+        f.submodules.add = self.add
+        f.submodules.sub = self.sub
+        f.comb += [
+            self.add.a.eq(self.a),
+            self.sub.a.eq(self.a),
+            self.add.b.eq(self.b),
+            self.sub.b.eq(self.b),
+        ]
+        with f.If(self.op):
+            f.comb += self.o.eq(self.sub.o)
+        with f.Else():
+            f.comb += self.o.eq(self.add.o)
+        return f.lower(platform)
+
+
+alu  = ALU(width=16)
+frag = alu.get_fragment(platform=None)
+# print(rtlil.convert(frag, ports=[alu.op, alu.a, alu.b, alu.o]))
+print(verilog.convert(frag, ports=[alu.op, alu.a, alu.b, alu.o, alu.add.o, alu.sub.o]))
diff --git a/examples/arst.py b/examples/arst.py
new file mode 100644 (file)
index 0000000..73d90fa
--- /dev/null
@@ -0,0 +1,21 @@
+from nmigen.fhdl import *
+from nmigen.back import rtlil, verilog
+
+
+class ClockDivisor:
+    def __init__(self, factor):
+        self.v = Signal(factor)
+        self.o = Signal()
+
+    def get_fragment(self, platform):
+        f = Module()
+        f.sync += self.v.eq(self.v + 1)
+        f.comb += self.o.eq(self.v[-1])
+        return f.lower(platform)
+
+
+sys  = ClockDomain(async_reset=True)
+ctr  = ClockDivisor(factor=16)
+frag = ctr.get_fragment(platform=None)
+# print(rtlil.convert(frag, ports=[sys.clk, sys.rst, ctr.o], clock_domains={"sys": sys}))
+print(verilog.convert(frag, ports=[sys.clk, sys.rst, ctr.o], clock_domains={"sys": sys}))
diff --git a/examples/clkdiv.py b/examples/clkdiv.py
new file mode 100644 (file)
index 0000000..f54505e
--- /dev/null
@@ -0,0 +1,21 @@
+from nmigen.fhdl import *
+from nmigen.back import rtlil, verilog
+
+
+class ClockDivisor:
+    def __init__(self, factor):
+        self.v = Signal(factor, reset=2**factor-1)
+        self.o = Signal()
+
+    def get_fragment(self, platform):
+        f = Module()
+        f.sync += self.v.eq(self.v + 1)
+        f.comb += self.o.eq(self.v[-1])
+        return f.lower(platform)
+
+
+sys  = ClockDomain()
+ctr  = ClockDivisor(factor=16)
+frag = ctr.get_fragment(platform=None)
+# print(rtlil.convert(frag, ports=[sys.clk, ctr.o], clock_domains={"sys": sys}))
+print(verilog.convert(frag, ports=[sys.clk, ctr.o], clock_domains={"sys": sys}))
diff --git a/examples/ctrl.py b/examples/ctrl.py
new file mode 100644 (file)
index 0000000..9e6d76c
--- /dev/null
@@ -0,0 +1,22 @@
+from nmigen.fhdl import *
+from nmigen.back import rtlil, verilog
+
+
+class ClockDivisor:
+    def __init__(self, factor):
+        self.v = Signal(factor, reset=2**factor-1)
+        self.o = Signal()
+        self.ce = Signal()
+
+    def get_fragment(self, platform):
+        f = Module()
+        f.sync += self.v.eq(self.v + 1)
+        f.comb += self.o.eq(self.v[-1])
+        return CEInserter(self.ce)(f.lower())
+
+
+sys  = ClockDomain()
+ctr  = ClockDivisor(factor=16)
+frag = ctr.get_fragment(platform=None)
+# print(rtlil.convert(frag, ports=[sys.clk, ctr.o, ctr.ce], clock_domains={"sys": sys}))
+print(verilog.convert(frag, ports=[sys.clk, ctr.o, ctr.ce], clock_domains={"sys": sys}))
diff --git a/examples/pmux.py b/examples/pmux.py
new file mode 100644 (file)
index 0000000..be6c0d4
--- /dev/null
@@ -0,0 +1,29 @@
+from nmigen.fhdl import *
+from nmigen.back import rtlil, verilog
+
+
+class ParMux:
+    def __init__(self, width):
+        self.s = Signal(3)
+        self.a = Signal(width)
+        self.b = Signal(width)
+        self.c = Signal(width)
+        self.o = Signal(width)
+
+    def get_fragment(self, platform):
+        f = Module()
+        with f.Case(self.s, "--1"):
+            f.comb += self.o.eq(self.a)
+        with f.Case(self.s, "-1-"):
+            f.comb += self.o.eq(self.b)
+        with f.Case(self.s, "1--"):
+            f.comb += self.o.eq(self.c)
+        with f.Case(self.s):
+            f.comb += self.o.eq(0)
+        return f.lower(platform)
+
+
+pmux = ParMux(width=16)
+frag = pmux.get_fragment(platform=None)
+# print(rtlil.convert(frag, ports=[pmux.s, pmux.a, pmux.b, pmux.c, pmux.o]))
+print(verilog.convert(frag, ports=[pmux.s, pmux.a, pmux.b, pmux.c, pmux.o]))
diff --git a/nmigen/back/__init__.py b/nmigen/back/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/nmigen/back/rtlil.py b/nmigen/back/rtlil.py
new file mode 100644 (file)
index 0000000..5d6f7d9
--- /dev/null
@@ -0,0 +1,469 @@
+import io
+import textwrap
+from collections import defaultdict, OrderedDict
+from contextlib import contextmanager
+
+from ..fhdl import ast, ir, xfrm
+
+
+class _Namer:
+    def __init__(self):
+        super().__init__()
+        self._index = 0
+        self._names = set()
+
+    def _make_name(self, name, local):
+        if name is None:
+            self._index += 1
+            name = "${}".format(self._index)
+        elif not local and name[0] not in "\\$":
+            name = "\\{}".format(name)
+        while name in self._names:
+            self._index += 1
+            name = "{}${}".format(name, self._index)
+        self._names.add(name)
+        return name
+
+
+class _Bufferer:
+    def __init__(self):
+        super().__init__()
+        self._buffer = io.StringIO()
+
+    def __str__(self):
+        return self._buffer.getvalue()
+
+    def _append(self, fmt, *args, **kwargs):
+        self._buffer.write(fmt.format(*args, **kwargs))
+
+    def _src(self, src):
+        if src:
+            self._append("  attribute \\src {}", repr(src))
+
+
+class _Builder(_Namer, _Bufferer):
+    def module(self, name=None):
+        name = self._make_name(name, local=False)
+        return _ModuleBuilder(self, name)
+
+
+class _ModuleBuilder(_Namer, _Bufferer):
+    def __init__(self, rtlil, name):
+        super().__init__()
+        self.rtlil = rtlil
+        self.name  = name
+
+    def __enter__(self):
+        self._append("attribute \\generator \"{}\"\n", "nMigen")
+        self._append("module {}\n", self.name)
+        return self
+
+    def __exit__(self, *args):
+        self._append("end\n")
+        self.rtlil._buffer.write(str(self))
+
+    def wire(self, width, port_id=None, port_kind=None, name=None, src=""):
+        self._src(src)
+        name = self._make_name(name, local=False)
+        if port_id is None:
+            self._append("  wire width {} {}\n", width, name)
+        else:
+            assert port_kind in ("input", "output", "inout")
+            self._append("  wire width {} {} {} {}\n", width, port_kind, port_id, name)
+        return name
+
+    def connect(self, lhs, rhs):
+        self._append("  connect {} {}\n", lhs, rhs)
+
+    def cell(self, kind, name=None, params={}, ports={}, src=""):
+        self._src(src)
+        name = self._make_name(name, local=True)
+        self._append("  cell {} {}\n", kind, name)
+        for param, value in params.items():
+            if isinstance(value, str):
+                value = repr(value)
+            else:
+                value = int(value)
+            self._append("    parameter \\{} {}\n", param, value)
+        for port, wire in ports.items():
+            self._append("    connect {} {}\n", port, wire)
+        self._append("  end\n")
+        return name
+
+    def process(self, name=None, src=""):
+        name = self._make_name(name, local=True)
+        return _ProcessBuilder(self, name, src)
+
+
+class _ProcessBuilder(_Bufferer):
+    def __init__(self, rtlil, name, src):
+        super().__init__()
+        self.rtlil = rtlil
+        self.name  = name
+        self.src   = src
+
+    def __enter__(self):
+        self._src(self.src)
+        self._append("  process {}\n", self.name)
+        return self
+
+    def __exit__(self, *args):
+        self._append("  end\n")
+        self.rtlil._buffer.write(str(self))
+
+    def case(self):
+        return _CaseBuilder(self, indent=2)
+
+    def sync(self, kind, cond=None):
+        return _SyncBuilder(self, kind, cond)
+
+
+class _CaseBuilder:
+    def __init__(self, rtlil, indent):
+        self.rtlil  = rtlil
+        self.indent = indent
+
+    def __enter__(self):
+        return self
+
+    def __exit__(self, *args):
+        pass
+
+    def assign(self, lhs, rhs):
+        self.rtlil._append("{}assign {} {}\n", "  " * self.indent, lhs, rhs)
+
+    def switch(self, cond):
+        return _SwitchBuilder(self.rtlil, cond, self.indent)
+
+
+class _SwitchBuilder:
+    def __init__(self, rtlil, cond, indent):
+        self.rtlil  = rtlil
+        self.cond   = cond
+        self.indent = indent
+
+    def __enter__(self):
+        self.rtlil._append("{}switch {}\n", "  " * self.indent, self.cond)
+        return self
+
+    def __exit__(self, *args):
+        self.rtlil._append("{}end\n", "  " * self.indent)
+
+    def case(self, value=None):
+        if value is None:
+            self.rtlil._append("{}case\n", "  " * (self.indent + 1))
+        else:
+            self.rtlil._append("{}case {}'{}\n", "  " * (self.indent + 1),
+                               len(value), value)
+        return _CaseBuilder(self.rtlil, self.indent + 2)
+
+
+class _SyncBuilder:
+    def __init__(self, rtlil, kind, cond):
+        self.rtlil = rtlil
+        self.kind  = kind
+        self.cond  = cond
+
+    def __enter__(self):
+        if self.cond is None:
+            self.rtlil._append("    sync {}\n", self.kind)
+        else:
+            self.rtlil._append("    sync {} {}\n", self.kind, self.cond)
+        return self
+
+    def __exit__(self, *args):
+        pass
+
+    def update(self, lhs, rhs):
+        self.rtlil._append("      update {} {}\n", lhs, rhs)
+
+
+class _ValueTransformer(xfrm.ValueTransformer):
+    operator_map = {
+        (1, "~"):    "$not",
+        (1, "-"):    "$neg",
+        (1, "b"):    "$reduce_bool",
+        (2, "+"):    "$add",
+        (2, "-"):    "$sub",
+        (2, "*"):    "$mul",
+        (2, "/"):    "$div",
+        (2, "%"):    "$mod",
+        (2, "**"):   "$pow",
+        (2, "<<<"):  "$sshl",
+        (2, ">>>"):  "$sshr",
+        (2, "&"):    "$and",
+        (2, "^"):    "$xor",
+        (2, "|"):    "$or",
+        (2, "=="):   "$eq",
+        (2, "!="):   "$ne",
+        (2, "<"):    "$lt",
+        (2, "<="):   "$le",
+        (2, ">"):    "$gt",
+        (2, ">="):   "$ge",
+        (3, "m"):    "$mux",
+    }
+
+    def __init__(self, rtlil):
+        self.rtlil  = rtlil
+        self.wires  = ast.ValueDict()
+        self.ports  = ast.ValueDict()
+        self.driven = ast.ValueDict()
+        self.is_lhs   = False
+        self.sub_name = None
+
+    def add_port(self, signal, kind=None):
+        if signal in self.driven:
+            self.ports[signal] = (len(self.ports), "output")
+        else:
+            self.ports[signal] = (len(self.ports), "input")
+
+    def add_driven(self, signal, sync):
+        self.driven[signal] = sync
+
+    @contextmanager
+    def lhs(self):
+        try:
+            self.is_lhs = True
+            yield
+        finally:
+            self.is_lhs = False
+
+    @contextmanager
+    def hierarchy(self, sub_name):
+        try:
+            self.sub_name = sub_name
+            yield
+        finally:
+            self.sub_name = None
+
+    def on_unknown(self, node):
+        if node is None:
+            return None
+        else:
+            super().visit_unknown(node)
+
+    def on_Const(self, node):
+        if isinstance(node.value, str):
+            return "{}'{}".format(node.nbits, node.value)
+        else:
+            return "{}'{:b}".format(node.nbits, node.value)
+
+    def on_Signal(self, node):
+        if node in self.wires:
+            wire_curr, wire_next = self.wires[node]
+        else:
+            if node in self.ports:
+                port_id, port_kind = self.ports[node]
+            else:
+                port_id = port_kind = None
+            if self.sub_name:
+                wire_name = "{}_{}".format(self.sub_name, node.name)
+            else:
+                wire_name = node.name
+            wire_curr = self.rtlil.wire(width=node.nbits, name=wire_name,
+                                        port_id=port_id, port_kind=port_kind)
+            if node in self.driven:
+                wire_next = self.rtlil.wire(width=node.nbits, name=wire_curr + "$next")
+            else:
+                wire_next = None
+            self.wires[node] = (wire_curr, wire_next)
+
+        if self.is_lhs:
+            if wire_next is None:
+                raise ValueError("Cannot return lhs for non-driven signal {}".format(repr(node)))
+            return wire_next
+        else:
+            return wire_curr
+
+    def on_Operator_unary(self, node):
+        arg, = node.operands
+        arg_bits, arg_sign = arg.bits_sign()
+        res_bits, res_sign = node.bits_sign()
+        res = self.rtlil.wire(width=res_bits)
+        self.rtlil.cell(self.operator_map[(1, node.op)], ports={
+            "\\A": self(arg),
+            "\\Y": res,
+        }, params={
+            "A_SIGNED": arg_sign,
+            "A_WIDTH": arg_bits,
+            "Y_WIDTH": res_bits,
+        })
+        return res
+
+    def match_bits_sign(self, node, new_bits, new_sign):
+        if isinstance(node, ast.Const):
+            return self(ast.Const(node.value, (new_bits, new_sign)))
+
+        node_bits, node_sign = node.bits_sign()
+        if new_bits > node_bits:
+            res = self.rtlil.wire(width=new_bits)
+            self.rtlil.cell("$pos", ports={
+                "\\A": self(node),
+                "\\Y": res,
+            }, params={
+                "A_SIGNED": node_sign,
+                "A_WIDTH": node_bits,
+                "Y_WIDTH": new_bits,
+            })
+            return res
+        else:
+            return "{} [{}:0]".format(self(node), new_bits - 1)
+
+    def on_Operator_binary(self, node):
+        lhs, rhs = node.operands
+        lhs_bits, lhs_sign = lhs.bits_sign()
+        rhs_bits, rhs_sign = rhs.bits_sign()
+        if lhs_sign == rhs_sign:
+            lhs_wire = self(lhs)
+            rhs_wire = self(rhs)
+        else:
+            lhs_sign = rhs_sign = True
+            lhs_bits = rhs_bits = max(lhs_bits, rhs_bits)
+            lhs_wire = self.match_bits_sign(lhs, lhs_bits, lhs_sign)
+            rhs_wire = self.match_bits_sign(rhs, rhs_bits, rhs_sign)
+        res_bits, res_sign = node.bits_sign()
+        res = self.rtlil.wire(width=res_bits)
+        self.rtlil.cell(self.operator_map[(2, node.op)], ports={
+            "\\A": lhs_wire,
+            "\\B": rhs_wire,
+            "\\Y": res,
+        }, params={
+            "A_SIGNED": lhs_sign,
+            "A_WIDTH": lhs_bits,
+            "B_SIGNED": rhs_sign,
+            "B_WIDTH": rhs_bits,
+            "Y_WIDTH": res_bits,
+        })
+        return res
+
+    def on_Operator_mux(self, node):
+        sel, lhs, rhs = node.operands
+        lhs_bits, lhs_sign = lhs.bits_sign()
+        rhs_bits, rhs_sign = rhs.bits_sign()
+        res_bits, res_sign = node.bits_sign()
+        res = self.rtlil.wire(width=res_bits)
+        self.rtlil.cell("$mux", ports={
+            "\\A": self(lhs),
+            "\\B": self(rhs),
+            "\\S": self(sel),
+            "\\Y": res,
+        }, params={
+            "WIDTH": max(lhs_bits, rhs_bits, res_bits)
+        })
+        return res
+
+    def on_Operator(self, node):
+        if len(node.operands) == 1:
+            return self.on_Operator_unary(node)
+        elif len(node.operands) == 2:
+            return self.on_Operator_binary(node)
+        elif len(node.operands) == 3:
+            assert node.op == "m"
+            return self.on_Operator_mux(node)
+        else:
+            raise TypeError
+
+    def on_Slice(self, node):
+        if node.end == node.start + 1:
+            return "{} [{}]".format(self(node.value), node.start)
+        else:
+            return "{} [{}:{}]".format(self(node.value), node.end - 1, node.start)
+
+    # def on_Part(self, node):
+    #     return _Part(self(node.value), self(node.offset), node.width)
+
+    def on_Cat(self, node):
+        return "{{ {} }}".format(" ".join(reversed([self(o) for o in node.operands])))
+
+    def on_Repl(self, node):
+        return "{{ {} }}".format(" ".join(self(node.value) for _ in range(node.count)))
+
+
+def convert_fragment(builder, fragment, name, clock_domains):
+    with builder.module(name) as module:
+        xformer = _ValueTransformer(module)
+
+        for cd_name, signal in fragment.iter_drivers():
+            xformer.add_driven(signal, sync=cd_name is not None)
+
+        for signal in fragment.ports:
+            xformer.add_port(signal)
+
+        for subfragment, sub_name in fragment.subfragments:
+            sub_name, sub_port_map = \
+                convert_fragment(builder, subfragment, sub_name, clock_domains)
+            with xformer.hierarchy(sub_name):
+                module.cell(sub_name, name=sub_name, ports={
+                    p: xformer(s) for p, s in sub_port_map.items()
+                })
+
+        with module.process() as process:
+            with process.case() as case:
+                for cd_name, signal in fragment.iter_drivers():
+                    if cd_name is None:
+                        prev_value = xformer(ast.Const(signal.reset, signal.nbits))
+                    else:
+                        prev_value = xformer(signal)
+                    with xformer.lhs():
+                        case.assign(xformer(signal), prev_value)
+
+                def _convert_stmts(case, stmts):
+                    for stmt in stmts:
+                        if isinstance(stmt, ast.Assign):
+                            lhs_bits, lhs_sign = stmt.lhs.bits_sign()
+                            rhs_bits, rhs_sign = stmt.rhs.bits_sign()
+                            if lhs_bits == rhs_bits:
+                                rhs_sigspec = xformer(stmt.rhs)
+                            else:
+                                rhs_sigspec = xformer.match_bits_sign(
+                                    stmt.rhs, lhs_bits, rhs_sign)
+                            with xformer.lhs():
+                                lhs_sigspec = xformer(stmt.lhs)
+                            case.assign(lhs_sigspec, rhs_sigspec)
+
+                        elif isinstance(stmt, ast.Switch):
+                            with case.switch(xformer(stmt.test)) as switch:
+                                for value, nested_stmts in stmt.cases.items():
+                                    with switch.case(value) as nested_case:
+                                        _convert_stmts(nested_case, nested_stmts)
+
+                        else:
+                            raise TypeError
+
+                _convert_stmts(case, fragment.statements)
+
+            with process.sync("init") as sync:
+                for cd_name, signal in fragment.iter_sync():
+                    sync.update(xformer(signal),
+                                xformer(ast.Const(signal.reset, signal.nbits)))
+
+            for cd_name, signals in fragment.iter_domains():
+                triggers = []
+                if cd_name is None:
+                    triggers.append(("always",))
+                else:
+                    cd = clock_domains[cd_name]
+                    triggers.append(("posedge", xformer(cd.clk)))
+                    if cd.async_reset:
+                        triggers.append(("posedge", xformer(cd.rst)))
+
+                for trigger in triggers:
+                    with process.sync(*trigger) as sync:
+                        for signal in signals:
+                            xformer(signal)
+                            wire_curr, wire_next = xformer.wires[signal]
+                            sync.update(wire_curr, wire_next)
+
+    port_map = OrderedDict()
+    for signal in fragment.ports:
+        port_map[xformer(signal)] = signal
+
+    return module.name, port_map
+
+
+def convert(fragment, ports=[], clock_domains={}):
+    fragment, ins, outs = fragment.prepare(ports, clock_domains)
+
+    builder = _Builder()
+    convert_fragment(builder, fragment, "top", clock_domains)
+    return str(builder)
diff --git a/nmigen/back/verilog.py b/nmigen/back/verilog.py
new file mode 100644 (file)
index 0000000..90a5c2f
--- /dev/null
@@ -0,0 +1,34 @@
+import os
+import subprocess
+
+from . import rtlil
+
+
+__all__ = ["convert"]
+
+
+class YosysError(Exception):
+    pass
+
+
+def convert(*args, **kwargs):
+    il_text = rtlil.convert(*args, **kwargs)
+    popen = subprocess.Popen([os.getenv("YOSYS", "yosys"), "-q", "-"],
+        stdin=subprocess.PIPE,
+        stdout=subprocess.PIPE,
+        stderr=subprocess.PIPE,
+        encoding="utf-8")
+    verilog_text, error = popen.communicate("""
+read_ilang <<rtlil
+{}
+rtlil
+proc_init
+proc_arst
+proc_dff
+proc_clean
+write_verilog
+""".format(il_text))
+    if popen.returncode:
+        raise YosysError(error.strip())
+    else:
+        return verilog_text
diff --git a/nmigen/fhdl/__init__.py b/nmigen/fhdl/__init__.py
new file mode 100644 (file)
index 0000000..c411310
--- /dev/null
@@ -0,0 +1,4 @@
+from .cd import ClockDomain
+from .ast import Value, Const, Mux, Cat, Repl, Signal, ClockSignal, ResetSignal
+from .dsl import Module
+from .xfrm import ResetInserter, CEInserter
diff --git a/nmigen/fhdl/ast.py b/nmigen/fhdl/ast.py
new file mode 100644 (file)
index 0000000..198b5c4
--- /dev/null
@@ -0,0 +1,728 @@
+from collections import OrderedDict
+from collections.abc import Iterable, MutableMapping, MutableSet
+
+from .. import tracer
+from ..tools import *
+
+
+__all__ = [
+    "Value", "Const", "Operator", "Mux", "Part", "Slice", "Cat", "Repl",
+    "Signal", "ClockSignal", "ResetSignal",
+    "Statement", "Assign", "Switch",
+    "ValueKey", "ValueDict", "ValueSet",
+]
+
+
+class DUID:
+    """Deterministic Unique IDentifier"""
+    __next_uid = 0
+    def __init__(self):
+        self.duid = DUID.__next_uid
+        DUID.__next_uid += 1
+
+
+class Value:
+    @staticmethod
+    def wrap(obj):
+        """Ensures that the passed object is a Migen value. Booleans and integers
+        are automatically wrapped into ``Const``."""
+        if isinstance(obj, Value):
+            return obj
+        elif isinstance(obj, (bool, int)):
+            return Const(obj)
+        else:
+            raise TypeError("Object {} of type {} is not a Migen value"
+                            .format(repr(obj), type(obj)))
+
+    def __bool__(self):
+        # Special case: Consts 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, Const) and isinstance(b, Const):
+                return a.value == b.value
+            if isinstance(a, Signal) and isinstance(b, Signal):
+                return a is b
+            if (isinstance(a, Const) and isinstance(b, Signal)
+                    or isinstance(a, Signal) and isinstance(b, Const)):
+                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 __mod__(self, other):
+        return Operator("%", [self, other])
+    def __rmod__(self, other):
+        return Operator("%", [other, self])
+    def __div__(self, other):
+        return Operator("/", [self, other])
+    def __rdiv__(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 __eq__(self, other):
+        return Operator("==", [self, other])
+    def __ne__(self, other):
+        return Operator("!=", [self, other])
+    def __lt__(self, other):
+        return Operator("<", [self, other])
+    def __le__(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):
+        return self.bits_sign()[0]
+
+    def __getitem__(self, key):
+        n = len(self)
+        if isinstance(key, int):
+            if key not in range(-n, n):
+                raise IndexError("Cannot index {} bits into {}-bit value".format(key, n))
+            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("Cannot index value with {}".format(repr(key)))
+
+    def bool(self):
+        """Conversion to boolean.
+
+        Returns
+        -------
+        Value, out
+            Output ``Value``. If any bits are set, returns ``1``, else ``0``.
+        """
+        return Operator("b", [self])
+
+    def part(self, offset, width):
+        """Indexed part-select.
+
+        Selects a constant width but variable offset part of a ``Value``.
+
+        Parameters
+        ----------
+        offset : Value, in
+            start point of the selected bits
+        width : int
+            number of selected bits
+
+        Returns
+        -------
+        Part, out
+            Selected part of the ``Value``
+        """
+        return Part(self, offset, width)
+
+    def eq(self, value):
+        """Assignment.
+
+        Parameters
+        ----------
+        value : Value, in
+            Value to be assigned.
+
+        Returns
+        -------
+        Assign
+            Assignment statement that can be used in combinatorial or synchronous context.
+        """
+        return Assign(self, value)
+
+    def bits_sign(self):
+        """Bit length and signedness of a 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(Signal(8))
+        8, False
+        >>> Value.bits_sign(C(0xaa))
+        8, False
+        """
+        raise TypeError("Cannot calculate bit length of {!r}".format(self))
+
+    def _lhs_signals(self):
+        raise TypeError("Value {!r} cannot be used in assignments".format(self))
+
+    def _rhs_signals(self):
+        raise NotImplementedError
+
+    def __hash__(self):
+        raise TypeError("Unhashable type: {}".format(type(self).__name__))
+
+
+class Const(Value):
+    """A constant, 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 `Const` and whether it is
+        signed (can represent negative values). `bits_sign` defaults
+        to the minimum width and signedness of `value`.
+
+    Attributes
+    ----------
+    nbits : int
+    signed : bool
+    """
+    def __init__(self, value, bits_sign=None):
+        self.value = int(value)
+        if bits_sign is None:
+            bits_sign = self.value.bit_length(), self.value < 0
+        if 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 positive integer")
+
+    def bits_sign(self):
+        return self.nbits, self.signed
+
+    def _rhs_signals(self):
+        return ValueSet()
+
+    def __eq__(self, other):
+        return self.value == other.value
+
+    def __hash__(self):
+        return hash(self.value)
+
+    def __repr__(self):
+        return "(const {}'{}d{})".format(self.nbits, "s" if self.signed else "", self.value)
+
+
+C = Const  # shorthand
+
+
+class Operator(Value):
+    def __init__(self, op, operands):
+        super().__init__()
+        self.op = op
+        self.operands = [Value.wrap(o) for o in operands]
+
+    @staticmethod
+    def _bitwise_binary_bits_sign(a, b):
+        if not a[1] and not b[1]:
+            # both operands unsigned
+            return max(a[0], b[0]), False
+        elif a[1] and b[1]:
+            # both operands signed
+            return max(a[0], b[0]), True
+        elif not a[1] and b[1]:
+            # first operand unsigned (add sign bit), second operand signed
+            return max(a[0] + 1, b[0]), True
+        else:
+            # first signed, second operand unsigned (add sign bit)
+            return max(a[0], b[0] + 1), True
+
+    def bits_sign(self):
+        obs = list(map(lambda x: x.bits_sign(), self.operands))
+        if self.op == "+" or self.op == "-":
+            if len(obs) == 1:
+                if self.op == "-" and not obs[0][1]:
+                    return obs[0][0] + 1, True
+                else:
+                    return obs[0]
+            n, s = self._bitwise_binary_bits_sign(*obs)
+            return n + 1, s
+        elif self.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 self.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 self.op == ">>>":
+            if obs[1][1]:
+                extra = 2**(obs[1][0] - 1)
+            else:
+                extra = 0
+            return obs[0][0] + extra, obs[0][1]
+        elif self.op == "&" or self.op == "^" or self.op == "|":
+            return self._bitwise_binary_bits_sign(*obs)
+        elif (self.op == "<" or self.op == "<=" or self.op == "==" or self.op == "!=" or
+              self.op == ">" or self.op == ">="):
+            return 1, False
+        elif self.op == "~":
+            return obs[0]
+        elif self.op == "m":
+            return _bitwise_binary_bits_sign(obs[1], obs[2])
+        else:
+            raise TypeError
+
+    def _rhs_signals(self):
+        return union(op._rhs_signals() for op in self.operands)
+
+    def __repr__(self):
+        if len(self.operands) == 1:
+            return "({} {})".format(self.op, self.operands[0])
+        elif len(self.operands) == 2:
+            return "({} {} {})".format(self.op, self.operands[0], self.operands[1])
+
+
+def Mux(sel, val1, val0):
+    """Choose between two values.
+
+    Parameters
+    ----------
+    sel : Value, in
+        Selector.
+    val1 : Value, in
+    val0 : Value, in
+        Input values.
+
+    Returns
+    -------
+    Value, 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, end):
+        if not isinstance(start, int):
+            raise TypeError("Slice start must be integer, not {!r}".format(start))
+        if not isinstance(end, int):
+            raise TypeError("Slice end must be integer, not {!r}".format(end))
+
+        n = len(value)
+        if start not in range(-n, n):
+            raise IndexError("Cannot start slice {} bits into {}-bit value".format(start, n))
+        if start < 0:
+            start += n
+        if end not in range(-(n+1), n+1):
+            raise IndexError("Cannot end slice {} bits into {}-bit value".format(end, n))
+        if end < 0:
+            end += n
+
+        super().__init__()
+        self.value = Value.wrap(value)
+        self.start = start
+        self.end   = end
+
+    def bits_sign(self):
+        return self.end - self.start, False
+
+    def _lhs_signals(self):
+        return self.value._lhs_signals()
+
+    def _rhs_signals(self):
+        return self.value._rhs_signals()
+
+    def __repr__(self):
+        return "(slice {} {}:{})".format(repr(self.value), self.start, self.end)
+
+
+class Part(Value):
+    def __init__(self, value, offset, width):
+        if not isinstance(width, int) or width < 0:
+            raise TypeError("Part width must be a positive integer, not {!r}".format(width))
+
+        super().__init__()
+        self.value  = value
+        self.offset = Value.wrap(offset)
+        self.width  = width
+
+    def bits_sign(self):
+        return self.width, False
+
+    def _lhs_signals(self):
+        return self.value._lhs_signals()
+
+    def _rhs_signals(self):
+        return self.value._rhs_signals()
+
+    def __repr__(self):
+        return "(part {} {})".format(repr(self.value), repr(self.offset), self.width)
+
+
+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
+    -------
+    Value, inout
+        Resulting ``Value`` obtained by concatentation.
+    """
+    def __init__(self, *args):
+        super().__init__()
+        self.operands = [Value.wrap(v) for v in flatten(args)]
+
+    def bits_sign(self):
+        return sum(len(op) for op in self.operands), False
+
+    def _lhs_signals(self):
+        return union(op._lhs_signals() for op in self.operands)
+
+    def _rhs_signals(self):
+        return union(op._rhs_signals() for op in self.operands)
+
+    def __repr__(self):
+        return "(cat {})".format(" ".join(map(repr, self.operands)))
+
+
+class Repl(Value):
+    """Replicate a value
+
+    An input value is replicated (repeated) several times
+    to be used on the RHS of assignments::
+
+        len(Repl(s, n)) == len(s) * n
+
+    Parameters
+    ----------
+    value : Value, in
+        Input value to be replicated.
+    count : int
+        Number of replications.
+
+    Returns
+    -------
+    Repl, out
+        Replicated value.
+    """
+    def __init__(self, value, count):
+        if not isinstance(count, int) or count < 0:
+            raise TypeError("Replication count must be a positive integer, not {!r}".format(count))
+
+        super().__init__()
+        self.value = Value.wrap(value)
+        self.count = count
+
+    def bits_sign(self):
+        return len(self.value) * self.count, False
+
+    def _rhs_signals(self):
+        return value._rhs_signals()
+
+
+class Signal(Value, DUID):
+    """A varying integer value.
+
+    Parameters
+    ----------
+    bits_sign : int or tuple or None
+        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).
+        ``bits_sign`` defaults to 1-bit and non-signed.
+    name : str
+        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.
+    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.
+
+    Attributes
+    ----------
+    nbits : int
+    signed : bool
+    name : str
+    reset : int
+    """
+
+    def __init__(self, bits_sign=1, reset=0, name=None):
+        super().__init__()
+
+        if name is None:
+            name = tracer.get_var_name()
+        self.name = name
+
+        if isinstance(bits_sign, int):
+            bits_sign = bits_sign, False
+        self.nbits, self.signed = bits_sign
+        if not isinstance(self.nbits, int) or self.nbits < 0:
+            raise TypeError("Width must be a positive integer")
+        self.reset = reset
+
+    def bits_sign(self):
+        return self.nbits, self.signed
+
+    def _lhs_signals(self):
+        return ValueSet((self,))
+
+    def _rhs_signals(self):
+        return ValueSet((self,))
+
+    def __repr__(self):
+        return "(sig {})".format(self.name)
+
+
+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"):
+        super().__init__()
+        if not isinstance(cd, str):
+            raise TypeError("Clock domain name must be a string, not {!r}".format(cd))
+        self.cd = cd
+
+    def __repr__(self):
+        return "(clk {})".format(self.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"`.
+    """
+    def __init__(self, cd="sys"):
+        super().__init__()
+        if not isinstance(cd, str):
+            raise TypeError("Clock domain name must be a string, not {!r}".format(cd))
+        self.cd = cd
+
+    def __repr__(self):
+        return "(rst {})".format(self.cd)
+
+
+class Statement:
+    @staticmethod
+    def wrap(obj):
+        if isinstance(obj, Iterable):
+            return sum((Statement.wrap(e) for e in obj), [])
+        else:
+            if isinstance(obj, Statement):
+                return [obj]
+            else:
+                raise TypeError("Object {!r} is not a Migen statement".format(obj))
+
+
+class Assign(Statement):
+    def __init__(self, lhs, rhs):
+        self.lhs = Value.wrap(lhs)
+        self.rhs = Value.wrap(rhs)
+
+    def _lhs_signals(self):
+        return self.lhs._lhs_signals()
+
+    def _rhs_signals(self):
+        return self.rhs._rhs_signals()
+
+    def __repr__(self):
+        return "(eq {!r} {!r})".format(self.lhs, self.rhs)
+
+
+class Switch(Statement):
+    def __init__(self, test, cases):
+        self.test  = Value.wrap(test)
+        self.cases = OrderedDict()
+        for key, stmts in cases.items():
+            if isinstance(key, (bool, int)):
+                key = "{:0{}b}".format(key, len(test))
+            elif isinstance(key, str):
+                assert len(key) == len(test)
+            else:
+                raise TypeError
+            if not isinstance(stmts, Iterable):
+                stmts = [stmts]
+            self.cases[key] = Statement.wrap(stmts)
+
+    def _lhs_signals(self):
+        return union(s._lhs_signals() for ss in self.cases.values() for s in ss )
+
+    def _rhs_signals(self):
+        signals = union(s._rhs_signals() for ss in self.cases.values() for s in ss)
+        return self.test._rhs_signals() | signals
+
+    def __repr__(self):
+        cases = ["(case {} {})".format(key, " ".join(map(repr, stmts)))
+                 for key, stmts in self.cases.items()]
+        return "(switch {!r} {})".format(self.test, " ".join(cases))
+
+
+class ValueKey:
+    def __init__(self, value):
+        self.value = Value.wrap(value)
+
+    def __hash__(self):
+        if isinstance(self.value, Const):
+            return hash(self.value)
+        elif isinstance(self.value, Signal):
+            return hash(id(self.value))
+        elif isinstance(self.value, Slice):
+            return hash((ValueKey(self.value.value), self.value.start, self.value.end))
+        else:
+            raise TypeError
+
+    def __eq__(self, other):
+        if not isinstance(other, ValueKey):
+            return False
+        if type(self.value) != type(other.value):
+            return False
+
+        if isinstance(self.value, Const):
+            return self.value == other.value
+        elif isinstance(self.value, Signal):
+            return id(self.value) == id(other.value)
+        elif isinstance(self.value, Slice):
+            return (ValueKey(self.value.value) == ValueKey(other.value.value) and
+                    self.value.start == other.value.start and
+                    self.value.end == other.value.end)
+        else:
+            raise TypeError
+
+    def __lt__(self, other):
+        if not isinstance(other, ValueKey):
+            return False
+        if type(self.value) != type(other.value):
+            return False
+
+        if isinstance(self.value, Const):
+            return self.value < other.value
+        elif isinstance(self.value, Signal):
+            return self.value.duid < other.value.duid
+        elif isinstance(self.value, Slice):
+            return (ValueKey(self.value.value) < ValueKey(other.value.value) and
+                    self.value.start < other.value.start and
+                    self.value.end < other.value.end)
+        else:
+            raise TypeError
+
+
+class ValueDict(MutableMapping):
+    def __init__(self, pairs=()):
+        self._inner = dict()
+        for key, value in pairs:
+            self[key] = value
+
+    def __getitem__(self, key):
+        key = None if key is None else ValueKey(key)
+        return self._inner[key]
+
+    def __setitem__(self, key, value):
+        key = None if key is None else ValueKey(key)
+        self._inner[key] = value
+
+    def __delitem__(self, key):
+        key = None if key is None else ValueKey(key)
+        del self._inner[key]
+
+    def __iter__(self):
+        return map(lambda x: None if x is None else x.value, sorted(self._inner))
+
+    def __len__(self):
+        return len(self._inner)
+
+
+class ValueSet(MutableSet):
+    def __init__(self, elements=()):
+        self._inner = set()
+        for elem in elements:
+            self.add(elem)
+
+    def add(self, value):
+        self._inner.add(ValueKey(value))
+
+    def update(self, values):
+        for value in values:
+            self.add(value)
+
+    def discard(self, value):
+        self._inner.discard(ValueKey(value))
+
+    def __contains__(self, value):
+        return ValueKey(value) in self._inner
+
+    def __iter__(self):
+        return map(lambda x: x.value, sorted(self._inner))
+
+    def __len__(self):
+        return len(self._inner)
+
+    def __repr__(self):
+        return "ValueSet({})".format(", ".join(repr(x) for x in self))
diff --git a/nmigen/fhdl/cd.py b/nmigen/fhdl/cd.py
new file mode 100644 (file)
index 0000000..5e21220
--- /dev/null
@@ -0,0 +1,48 @@
+from .. import tracer
+from .ast import Signal
+
+
+__all__ = ["ClockDomain"]
+
+
+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
+        If ``True``, 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.
+    async_reset : bool
+        If ``True``, the domain uses an asynchronous reset, and registers within this domain
+        are initialized to their reset state when reset level changes. Otherwise, registers
+        are initialized to reset state at the next clock cycle when reset is asserted.
+
+    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, async_reset=False):
+        if name is None:
+            name = tracer.get_var_name()
+        if name is None:
+            raise ValueError("Clock domain name must be specified explicitly")
+        if name.startswith("cd_"):
+            name = name[3:]
+        self.name = name
+
+        self.clk = Signal(name=self.name + "_clk")
+        if reset_less:
+            self.rst = None
+        else:
+            self.rst = Signal(name=self.name + "_rst")
+
+        self.async_reset = async_reset
diff --git a/nmigen/fhdl/dsl.py b/nmigen/fhdl/dsl.py
new file mode 100644 (file)
index 0000000..b193e12
--- /dev/null
@@ -0,0 +1,240 @@
+from collections import OrderedDict
+
+from .ast import *
+from .ir import *
+from .xfrm import *
+
+
+__all__ = ["Module"]
+
+
+class _ModuleBuilderProxy:
+    def __init__(self, builder, depth):
+        object.__setattr__(self, "_builder", builder)
+        object.__setattr__(self, "_depth", depth)
+
+
+class _ModuleBuilderComb(_ModuleBuilderProxy):
+    def __iadd__(self, assigns):
+        self._builder._add_statement(assigns, cd=None, depth=self._depth)
+        return self
+
+
+class _ModuleBuilderSyncCD(_ModuleBuilderProxy):
+    def __init__(self, builder, depth, cd):
+        super().__init__(builder, depth)
+        self._cd = cd
+
+    def __iadd__(self, assigns):
+        self._builder._add_statement(assigns, cd=self._cd, depth=self._depth)
+        return self
+
+
+class _ModuleBuilderSync(_ModuleBuilderProxy):
+    def __iadd__(self, assigns):
+        self._builder._add_statement(assigns, cd="sys", depth=self._depth)
+        return self
+
+    def __getattr__(self, name):
+        return _ModuleBuilderSyncCD(self._builder, self._depth, name)
+
+    def __setattr__(self, name, value):
+        if not isinstance(value, _ModuleBuilderSyncCD):
+            raise AttributeError("Cannot assign sync.{} attribute - use += instead"
+                                 .format(name))
+
+
+class _ModuleBuilderRoot:
+    def __init__(self, builder, depth):
+        self._builder = builder
+        self.comb = _ModuleBuilderComb(builder, depth)
+        self.sync = _ModuleBuilderSync(builder, depth)
+
+    def __setattr__(self, name, value):
+        if name == "comb" and not isinstance(value, _ModuleBuilderComb):
+            raise AttributeError("Cannot assign comb attribute - use += instead")
+        if name == "sync" and not isinstance(value, _ModuleBuilderSync):
+            raise AttributeError("Cannot assign sync attribute - use += instead")
+        super().__setattr__(name, value)
+
+
+class _ModuleBuilderIf(_ModuleBuilderRoot):
+    def __init__(self, builder, depth, cond):
+        super().__init__(builder, depth)
+        self._cond = cond
+
+    def __enter__(self):
+        self._builder._flush()
+        self._builder._stmt_if_cond.append(self._cond)
+        self._outer_case = self._builder._statements
+        self._builder._statements = []
+        return self
+
+    def __exit__(self, *args):
+        self._builder._stmt_if_bodies.append(self._builder._statements)
+        self._builder._statements = self._outer_case
+
+
+class _ModuleBuilderElif(_ModuleBuilderRoot):
+    def __init__(self, builder, depth, cond):
+        super().__init__(builder, depth)
+        self._cond = cond
+
+    def __enter__(self):
+        if not self._builder._stmt_if_cond:
+            raise ValueError("Elif without preceding If")
+        self._builder._stmt_if_cond.append(self._cond)
+        self._outer_case = self._builder._statements
+        self._builder._statements = []
+        return self
+
+    def __exit__(self, *args):
+        self._builder._stmt_if_bodies.append(self._builder._statements)
+        self._builder._statements = self._outer_case
+
+
+class _ModuleBuilderElse(_ModuleBuilderRoot):
+    def __init__(self, builder, depth):
+        super().__init__(builder, depth)
+
+    def __enter__(self):
+        if not self._builder._stmt_if_cond:
+            raise ValueError("Else without preceding If/Elif")
+        self._builder._stmt_if_cond.append(1)
+        self._outer_case = self._builder._statements
+        self._builder._statements = []
+        return self
+
+    def __exit__(self, *args):
+        self._builder._stmt_if_bodies.append(self._builder._statements)
+        self._builder._statements = self._outer_case
+        self._builder._flush()
+
+
+class _ModuleBuilderCase(_ModuleBuilderRoot):
+    def __init__(self, builder, depth, test, value):
+        super().__init__(builder, depth)
+        self._test  = test
+        self._value = value
+
+    def __enter__(self):
+        if self._value is None:
+            self._value = "-" * len(self._test)
+        if isinstance(self._value, str) and len(self._test) != len(self._value):
+            raise ValueError("Case value {} must have the same width as test {}"
+                             .format(self._value, self._test))
+        if self._builder._stmt_switch_test != ValueKey(self._test):
+            self._builder._flush()
+            self._builder._stmt_switch_test = ValueKey(self._test)
+        self._outer_case = self._builder._statements
+        self._builder._statements = []
+        return self
+
+    def __exit__(self, *args):
+        self._builder._stmt_switch_cases[self._value] = self._builder._statements
+        self._builder._statements = self._outer_case
+
+
+class _ModuleBuilderSubmodules:
+    def __init__(self, builder):
+        object.__setattr__(self, "_builder", builder)
+
+    def __iadd__(self, submodules):
+        for submodule in submodules:
+            self._builder._add_submodule(submodule)
+        return self
+
+    def __setattr__(self, name, submodule):
+        self._builder._add_submodule(submodule, name)
+
+
+class Module(_ModuleBuilderRoot):
+    def __init__(self):
+        _ModuleBuilderRoot.__init__(self, self, depth=0)
+        self.submodules = _ModuleBuilderSubmodules(self)
+
+        self._submodules        = []
+        self._driving           = ValueDict()
+        self._statements        = []
+        self._stmt_depth        = 0
+        self._stmt_if_cond      = []
+        self._stmt_if_bodies    = []
+        self._stmt_switch_test  = None
+        self._stmt_switch_cases = OrderedDict()
+
+    def If(self, cond):
+        return _ModuleBuilderIf(self, self._stmt_depth + 1, cond)
+
+    def Elif(self, cond):
+        return _ModuleBuilderElif(self, self._stmt_depth + 1, cond)
+
+    def Else(self):
+        return _ModuleBuilderElse(self, self._stmt_depth + 1)
+
+    def Case(self, test, value=None):
+        return _ModuleBuilderCase(self, self._stmt_depth + 1, test, value)
+
+    def _flush(self):
+        if self._stmt_if_cond:
+            tests, cases = [], OrderedDict()
+            for if_cond, if_case in zip(self._stmt_if_cond, self._stmt_if_bodies):
+                if_cond = Value.wrap(if_cond)
+                if len(if_cond) != 1:
+                    if_cond = if_cond.bool()
+                tests.append(if_cond)
+
+                match = ("1" + "-" * (len(tests) - 1)).rjust(len(self._stmt_if_cond), "-")
+                cases[match] = if_case
+            self._statements.append(Switch(Cat(tests), cases))
+
+        if self._stmt_switch_test:
+            self._statements.append(Switch(self._stmt_switch_test.value, self._stmt_switch_cases))
+
+        self._stmt_if_cond      = []
+        self._stmt_if_bodies    = []
+        self._stmt_switch_test  = None
+        self._stmt_switch_cases = OrderedDict()
+
+    def _add_statement(self, assigns, cd, depth):
+        def cd_name(cd):
+            if cd is None:
+                return "comb"
+            else:
+                return "sync.{}".format(cd)
+
+        if depth < self._stmt_depth:
+            self._flush()
+        self._stmt_depth = depth
+
+        for assign in Statement.wrap(assigns):
+            if not isinstance(assign, Assign):
+                raise TypeError("Only assignments can be appended to {}".format(self.cd_name(cd)))
+
+            for signal in assign.lhs._lhs_signals():
+                if signal not in self._driving:
+                    self._driving[signal] = cd
+                elif self._driving[signal] != cd:
+                    cd_curr = self._driving[signal]
+                    raise ValueError("Driver-driver conflict: trying to drive {!r} from {}, but "
+                                     "it is already driven from {}"
+                                     .format(signal, self.cd_name(cd), self.cd_name(cd_curr)))
+
+            self._statements.append(assign)
+
+    def _add_submodule(self, submodule, name=None):
+        if not hasattr(submodule, "get_fragment"):
+            raise TypeError("Trying to add {!r}, which does not have .get_fragment(), as "
+                            " a submodule")
+        self._submodules.append((submodule, name))
+
+    def lower(self, platform):
+        self._flush()
+
+        fragment = Fragment()
+        for submodule, name in self._submodules:
+            fragment.add_subfragment(submodule.get_fragment(platform), name)
+        fragment.add_statements(self._statements)
+        for signal, cd_name in self._driving.items():
+            for lhs_signal in signal._lhs_signals():
+                fragment.drive(lhs_signal, cd_name)
+        return fragment
diff --git a/nmigen/fhdl/ir.py b/nmigen/fhdl/ir.py
new file mode 100644 (file)
index 0000000..5a00cc4
--- /dev/null
@@ -0,0 +1,74 @@
+from collections import defaultdict, OrderedDict
+
+from ..tools import *
+from .ast import *
+
+
+__all__ = ["Fragment"]
+
+
+class Fragment:
+    def __init__(self):
+        self.ports = ValueSet()
+        self.drivers = OrderedDict()
+        self.statements = []
+        self.subfragments = []
+
+    def add_ports(self, *ports):
+        self.ports.update(flatten(ports))
+
+    def iter_ports(self):
+        yield from self.ports
+
+    def drive(self, signal, cd_name=None):
+        if cd_name not in self.drivers:
+            self.drivers[cd_name] = ValueSet()
+        self.drivers[cd_name].add(signal)
+
+    def iter_domains(self):
+        yield from self.drivers.items()
+
+    def iter_drivers(self):
+        for cd_name, signals in self.drivers.items():
+            for signal in signals:
+                yield cd_name, signal
+
+    def iter_comb(self):
+        yield from self.drivers[None]
+
+    def iter_sync(self):
+        for cd_name, signals in self.drivers.items():
+            if cd_name is None:
+                continue
+            for signal in signals:
+                yield cd_name, signal
+
+    def add_statements(self, *stmts):
+        self.statements += Statement.wrap(stmts)
+
+    def add_subfragment(self, subfragment, name=None):
+        assert isinstance(subfragment, Fragment)
+        self.subfragments.append((subfragment, name))
+
+    def prepare(self, ports, clock_domains):
+        from .xfrm import ResetInserter
+
+        resets = {cd.name: cd.rst for cd in clock_domains.values() if cd.rst is not None}
+        frag   = ResetInserter(resets)(self)
+
+        self_driven = union(s._lhs_signals() for s in self.statements)
+        self_used   = union(s._rhs_signals() for s in self.statements)
+
+        ins  = self_used - self_driven
+        outs = ports & self_driven
+
+        for n, (subfrag, name) in enumerate(frag.subfragments):
+            subfrag, sub_ins, sub_outs = subfrag.prepare(ports=self_used | ports,
+                                                         clock_domains=clock_domains)
+            frag.subfragments[n] = (subfrag, name)
+            ins  |= sub_ins - self_driven
+            outs |= ports & sub_outs
+
+        frag.add_ports(ins, outs)
+
+        return frag, ins, outs
diff --git a/nmigen/fhdl/xfrm.py b/nmigen/fhdl/xfrm.py
new file mode 100644 (file)
index 0000000..0e95ec4
--- /dev/null
@@ -0,0 +1,124 @@
+from collections import OrderedDict
+
+from .ast import *
+from .ir import *
+
+
+__all__ = ["ValueTransformer", "StatementTransformer", "ResetInserter", "CEInserter"]
+
+
+class ValueTransformer:
+    def on_Const(self, value):
+        return value
+
+    def on_Signal(self, value):
+        return value
+
+    def on_ClockSignal(self, value):
+        return value
+
+    def on_ResetSignal(self, value):
+        return value
+
+    def on_Operator(self, value):
+        return Operator(value.op, [self.on_value(o) for o in value.operands])
+
+    def on_Slice(self, value):
+        return Slice(self.on_value(value.value), value.start, value.end)
+
+    def on_Part(self, value):
+        return Part(self.on_value(value.value), self.on_value(value.offset), value.width)
+
+    def on_Cat(self, value):
+        return Cat(self.on_value(o) for o in value.operands)
+
+    def on_Repl(self, value):
+        return Repl(self.on_value(value.value), value.count)
+
+    def on_value(self, value):
+        if isinstance(value, Const):
+            return self.on_Const(value)
+        elif isinstance(value, Signal):
+            return self.on_Signal(value)
+        elif isinstance(value, ClockSignal):
+            return self.on_ClockSignal(value)
+        elif isinstance(value, ResetSignal):
+            return self.on_ResetSignal(value)
+        elif isinstance(value, Operator):
+            return self.on_Operator(value)
+        elif isinstance(value, Slice):
+            return self.on_Slice(value)
+        elif isinstance(value, Part):
+            return self.on_Part(value)
+        elif isinstance(value, Cat):
+            return self.on_Cat(value)
+        elif isinstance(value, Repl):
+            return self.on_Repl(value)
+        else:
+            raise TypeError("Cannot transform value {!r}".format(value))
+
+    def __call__(self, value):
+        return self.on_value(value)
+
+
+class StatementTransformer:
+    def on_value(self, value):
+        return value
+
+    def on_Assign(self, stmt):
+        return Assign(self.on_value(stmt.lhs), self.on_value(stmt.rhs))
+
+    def on_Switch(self, stmt):
+        cases = OrderedDict((k, self.on_value(v)) for k, v in stmt.cases.items())
+        return Switch(self.on_value(stmt.test), cases)
+
+    def on_statements(self, stmt):
+        return list(flatten(self.on_statement(stmt) for stmt in self.on_statement(stmt)))
+
+    def on_statement(self, stmt):
+        if isinstance(stmt, Assign):
+            return self.on_Assign(stmt)
+        elif isinstance(stmt, Switch):
+            return self.on_Switch(stmt)
+        elif isinstance(stmt, (list, tuple)):
+            return self.on_statements(stmt)
+        else:
+            raise TypeError("Cannot transform statement {!r}".format(stmt))
+
+    def __call__(self, value):
+        return self.on_statement(value)
+
+
+class _ControlInserter:
+    def __init__(self, controls):
+        if isinstance(controls, Value):
+            controls = {"sys": controls}
+        self.controls = OrderedDict(controls)
+
+    def __call__(self, fragment):
+        new_fragment = Fragment()
+        for subfragment, name in fragment.subfragments:
+            new_fragment.add_subfragment(self(subfragment), name)
+        new_fragment.add_statements(fragment.statements)
+        for cd_name, signals in fragment.iter_domains():
+            for signal in signals:
+                new_fragment.drive(signal, cd_name)
+            if cd_name is None or cd_name not in self.controls:
+                continue
+            self._wrap_control(new_fragment, cd_name, signals)
+        return new_fragment
+
+    def _wrap_control(self, fragment, cd_name, signals):
+        raise NotImplementedError
+
+
+class ResetInserter(_ControlInserter):
+    def _wrap_control(self, fragment, cd_name, signals):
+        stmts = [s.eq(Const(s.reset, s.nbits)) for s in signals]
+        fragment.add_statements(Switch(self.controls[cd_name], {1: stmts}))
+
+
+class CEInserter(_ControlInserter):
+    def _wrap_control(self, fragment, cd_name, signals):
+        stmts = [s.eq(s) for s in signals]
+        fragment.add_statements(Switch(self.controls[cd_name], {0: stmts}))
diff --git a/nmigen/tools.py b/nmigen/tools.py
new file mode 100644 (file)
index 0000000..8f8e7c1
--- /dev/null
@@ -0,0 +1,22 @@
+from collections import Iterable
+
+
+__all__ = ["flatten", "union"]
+
+
+def flatten(i):
+    for e in i:
+        if isinstance(e, Iterable):
+            yield from flatten(e)
+        else:
+            yield e
+
+
+def union(i):
+    r = None
+    for e in i:
+        if r is None:
+            r = e
+        else:
+            r |= e
+    return r
diff --git a/nmigen/tracer.py b/nmigen/tracer.py
new file mode 100644 (file)
index 0000000..e3f9f93
--- /dev/null
@@ -0,0 +1,36 @@
+import inspect
+from opcode import opname
+
+
+class NameNotFound(Exception):
+    pass
+
+
+def get_var_name(depth=2):
+    frame = inspect.currentframe()
+    for _ in range(depth):
+        frame = frame.f_back
+
+    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_KW":
+        return None
+
+    index = call_index + 2
+    while True:
+        opc = opname[code.co_code[index]]
+        if opc in ("STORE_NAME", "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 in ("LOAD_GLOBAL", "LOAD_ATTR", "LOAD_FAST", "LOAD_DEREF",
+                     "DUP_TOP", "BUILD_LIST"):
+            index += 2
+        else:
+            raise NameNotFound
diff --git a/setup.py b/setup.py
new file mode 100644 (file)
index 0000000..2ab0a80
--- /dev/null
+++ b/setup.py
@@ -0,0 +1,16 @@
+import os
+from os import path
+
+from setuptools import setup, find_packages
+
+
+setup(
+    name="nmigen",
+    version="0.1",
+    author="whitequark",
+    author_email="whitequark@whitequark.org",
+    description="Python toolbox for building complex digital hardware",
+    #long_description="""TODO""",
+    license="BSD",
+    packages=find_packages(),
+)