--- /dev/null
+*.pyc
+*.egg-info
+*.il
+*.v
--- /dev/null
+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>
--- /dev/null
+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]))
--- /dev/null
+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]))
--- /dev/null
+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}))
--- /dev/null
+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}))
--- /dev/null
+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}))
--- /dev/null
+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]))
--- /dev/null
+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)
--- /dev/null
+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
--- /dev/null
+from .cd import ClockDomain
+from .ast import Value, Const, Mux, Cat, Repl, Signal, ClockSignal, ResetSignal
+from .dsl import Module
+from .xfrm import ResetInserter, CEInserter
--- /dev/null
+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))
--- /dev/null
+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
--- /dev/null
+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
--- /dev/null
+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
--- /dev/null
+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}))
--- /dev/null
+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
--- /dev/null
+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
--- /dev/null
+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(),
+)