Initial commit.

This commit is contained in:
whitequark 2018-12-11 20:50:56 +00:00
commit 4d3258013d
20 changed files with 2231 additions and 0 deletions

4
.gitignore vendored Normal file
View file

@ -0,0 +1,4 @@
*.pyc
*.egg-info
*.il
*.v

251
README.md Normal file
View file

@ -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>

29
examples/alu.py Normal file
View file

@ -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]))

59
examples/alu_hier.py Normal file
View file

@ -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]))

21
examples/arst.py Normal file
View file

@ -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}))

21
examples/clkdiv.py Normal file
View file

@ -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}))

22
examples/ctrl.py Normal file
View file

@ -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}))

29
examples/pmux.py Normal file
View file

@ -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]))

0
nmigen/back/__init__.py Normal file
View file

469
nmigen/back/rtlil.py Normal file
View file

@ -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)

34
nmigen/back/verilog.py Normal file
View file

@ -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

4
nmigen/fhdl/__init__.py Normal file
View file

@ -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

728
nmigen/fhdl/ast.py Normal file
View file

@ -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))

48
nmigen/fhdl/cd.py Normal file
View file

@ -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

240
nmigen/fhdl/dsl.py Normal file
View file

@ -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

74
nmigen/fhdl/ir.py Normal file
View file

@ -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

124
nmigen/fhdl/xfrm.py Normal file
View file

@ -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}))

22
nmigen/tools.py Normal file
View file

@ -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

36
nmigen/tracer.py Normal file
View file

@ -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

16
setup.py Normal file
View file

@ -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(),
)