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