Initial commit.
This commit is contained in:
commit
4d3258013d
4
.gitignore
vendored
Normal file
4
.gitignore
vendored
Normal file
|
@ -0,0 +1,4 @@
|
|||
*.pyc
|
||||
*.egg-info
|
||||
*.il
|
||||
*.v
|
251
README.md
Normal file
251
README.md
Normal 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
29
examples/alu.py
Normal 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
59
examples/alu_hier.py
Normal 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
21
examples/arst.py
Normal 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
21
examples/clkdiv.py
Normal 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
22
examples/ctrl.py
Normal 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
29
examples/pmux.py
Normal 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
0
nmigen/back/__init__.py
Normal file
469
nmigen/back/rtlil.py
Normal file
469
nmigen/back/rtlil.py
Normal 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
34
nmigen/back/verilog.py
Normal 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
4
nmigen/fhdl/__init__.py
Normal 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
728
nmigen/fhdl/ast.py
Normal 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
48
nmigen/fhdl/cd.py
Normal 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
240
nmigen/fhdl/dsl.py
Normal 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
74
nmigen/fhdl/ir.py
Normal 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
124
nmigen/fhdl/xfrm.py
Normal 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
22
nmigen/tools.py
Normal 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
36
nmigen/tracer.py
Normal 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
16
setup.py
Normal 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(),
|
||||
)
|
Loading…
Reference in a new issue