fhdl.ir: add black-box fragments, fragment parameters, and Instance.
authorwhitequark <cz@m-labs.hk>
Mon, 17 Dec 2018 22:55:30 +0000 (22:55 +0000)
committerwhitequark <cz@m-labs.hk>
Mon, 17 Dec 2018 22:55:39 +0000 (22:55 +0000)
examples/inst.py [new file with mode: 0644]
nmigen/__init__.py
nmigen/back/rtlil.py
nmigen/hdl/ast.py
nmigen/hdl/ir.py
nmigen/hdl/xfrm.py
nmigen/test/test_hdl_ir.py

diff --git a/examples/inst.py b/examples/inst.py
new file mode 100644 (file)
index 0000000..9b011d2
--- /dev/null
@@ -0,0 +1,26 @@
+from nmigen import *
+from nmigen.back import rtlil, verilog
+
+
+class System:
+    def __init__(self):
+        self.adr   = Signal(16)
+        self.dat_r = Signal(8)
+        self.dat_w = Signal(8)
+        self.we    = Signal()
+
+    def get_fragment(self, platform):
+        m = Module()
+        m.submodules += Instance("CPU",
+            p_RESET_ADDR=0xfff0,
+            i_d_adr  =self.adr,
+            i_d_dat_r=self.dat_r,
+            o_d_dat_w=self.dat_w,
+        )
+        return m.lower(platform)
+
+
+sys  = System()
+frag = sys.get_fragment(platform=None)
+# print(rtlil.convert(frag, ports=[sys.adr, sys.dat_r, sys.dat_w, sys.we]))
+print(verilog.convert(frag, ports=[sys.adr, sys.dat_r, sys.dat_w, sys.we]))
index 8dcd2cfe58edc63cd5cccac9edb0505d0a9383ae..cbd40fbec4b765839cce1c8df246db6c57c54c15 100644 (file)
@@ -1,7 +1,7 @@
 from .hdl.ast import Value, Const, Mux, Cat, Repl, Array, Signal, ClockSignal, ResetSignal
 from .hdl.dsl import Module
 from .hdl.cd import ClockDomain
-from .hdl.ir import Fragment
+from .hdl.ir import Fragment, Instance
 from .hdl.xfrm import ResetInserter, CEInserter
 
 from .lib.cdc import MultiReg
index 4ab2d8c649e5ad9eae4a6f289a5a4032376238e5..e46d9292c099c531a276eb503392b115fc04af4a 100644 (file)
@@ -567,6 +567,16 @@ class _StatementCompiler(xfrm.AbstractStatementTransformer):
 
 
 def convert_fragment(builder, fragment, name, top):
+    if fragment.black_box is not None:
+        port_map = OrderedDict()
+        for signal in fragment.ports:
+            port_map["\\{}".format(fragment.port_names[signal])] = signal
+
+        return "\\{}".format(fragment.black_box), port_map
+    else:
+        assert not fragment.port_names
+        assert not fragment.parameters
+
     with builder.module(name or "anonymous", attrs={"top": 1} if top else {}) as module:
         compiler_state = _ValueCompilerState(module)
         rhs_compiler   = _RHSValueCompiler(compiler_state)
@@ -601,7 +611,7 @@ def convert_fragment(builder, fragment, name, top):
             module.cell(sub_name, name=sub_name, ports={
                 port: compiler_state.resolve_curr(signal, prefix=sub_name)
                 for port, signal in sub_port_map.items()
-            })
+            }, params=subfragment.parameters)
 
         with module.process() as process:
             with process.case() as case:
index 33a3625fa61e8a76279e9213332cef801ceac48d..0eb8275f843a4abb3430eef6abc3d84bb55301b4 100644 (file)
@@ -887,11 +887,11 @@ class Passive(Statement):
 class _MappedKeyCollection(metaclass=ABCMeta):
     @abstractmethod
     def _map_key(self, key):
-        pass
+        pass # :nocov:
 
     @abstractmethod
     def _unmap_key(self, key):
-        pass
+        pass # :nocov:
 
 
 class _MappedKeyDict(MutableMapping, _MappedKeyCollection):
index b9dd84defb111af8cbe82a65c0602a39ec009ed0..adc5bc417a78fe995aacab65607973bca18e8e04 100644 (file)
@@ -6,7 +6,7 @@ from .ast import *
 from .cd import *
 
 
-__all__ = ["Fragment", "DriverConflict"]
+__all__ = ["Fragment", "Instance", "DriverConflict"]
 
 
 class DriverConflict(UserWarning):
@@ -15,19 +15,28 @@ class DriverConflict(UserWarning):
 
 class Fragment:
     def __init__(self):
+        self.black_box = None
+        self.port_names = SignalDict()
+        self.parameters = OrderedDict()
+
         self.ports = SignalDict()
         self.drivers = OrderedDict()
         self.statements = []
         self.domains = OrderedDict()
         self.subfragments = []
 
-    def add_ports(self, *ports, kind):
-        assert kind in ("i", "o", "io")
+    def add_ports(self, *ports, dir):
+        assert dir in ("i", "o", "io")
         for port in flatten(ports):
-            self.ports[port] = kind
+            self.ports[port] = dir
 
-    def iter_ports(self):
-        yield from self.ports.keys()
+    def iter_ports(self, dir=None):
+        if dir is None:
+            yield from self.ports
+        else:
+            for port, port_dir in self.ports.items():
+                if port_dir == dir:
+                    yield port
 
     def add_driver(self, signal, domain=None):
         if domain not in self.drivers:
@@ -78,6 +87,9 @@ class Fragment:
         assert isinstance(subfragment, Fragment)
         self.subfragments.append((subfragment, name))
 
+    def get_fragment(self, platform):
+        return self
+
     def _resolve_driver_conflicts(self, hierarchy=("top",), mode="warn"):
         assert mode in ("silent", "warn", "error")
 
@@ -248,7 +260,7 @@ class Fragment:
         for subfrag, name in self.subfragments:
             # Always ask subfragments to provide all signals we're using and signals we're asked
             # to provide. If the subfragment is not driving it, it will silently ignore it.
-            sub_ins, sub_outs = subfrag._propagate_ports(ports=self_used | ports)
+            sub_ins, sub_outs, sub_inouts = subfrag._propagate_ports(ports=self_used | ports)
             # Refine the input port approximation: if a subfragment is driving a signal,
             # it is definitely not our input. But, if a subfragment requires a signal as an input,
             # and we aren't driving it, it has to be our input as well.
@@ -257,12 +269,17 @@ class Fragment:
             # Refine the output port approximation: if a subfragment is driving a signal,
             # and we're asked to provide it, we can provide it now.
             outs |= ports & sub_outs
+            # All of our subfragments' bidirectional ports are also our bidirectional ports,
+            # since these are only used for pins.
+            self.add_ports(sub_inouts, dir="io")
 
         # We've computed the precise set of input and output ports.
-        self.add_ports(ins,  kind="i")
-        self.add_ports(outs, kind="o")
+        self.add_ports(ins,  dir="i")
+        self.add_ports(outs, dir="o")
 
-        return ins, outs
+        return (SignalSet(self.iter_ports("i")),
+                SignalSet(self.iter_ports("o")),
+                SignalSet(self.iter_ports("io")))
 
     def prepare(self, ports=(), ensure_sync_exists=True):
         from .xfrm import FragmentTransformer
@@ -274,3 +291,24 @@ class Fragment:
         fragment = fragment._lower_domain_signals()
         fragment._propagate_ports(ports)
         return fragment
+
+
+class Instance(Fragment):
+    def __init__(self, type, **kwargs):
+        super().__init__()
+        self.black_box = type
+        for kw, arg in kwargs.items():
+            if kw.startswith("p_"):
+                self.parameters[kw[2:]] = arg
+            elif kw.startswith("i_"):
+                self.port_names[arg] = kw[2:]
+                self.add_ports(arg, dir="i")
+            elif kw.startswith("o_"):
+                self.port_names[arg] = kw[2:]
+                self.add_ports(arg, dir="o")
+            elif kw.startswith("io_"):
+                self.port_names[arg] = kw[3:]
+                self.add_ports(arg, dir="io")
+            else:
+                raise NameError("Instance argument '{}' does not start with p_, i_, o_, or io_"
+                                .format(arg))
index 62f7fea697ed1e33a6d87b5e8910bab2b2147457..36b0c9827740b7f37b550806db0d1a0de092a650 100644 (file)
@@ -18,43 +18,43 @@ __all__ = ["AbstractValueTransformer", "ValueTransformer",
 class AbstractValueTransformer(metaclass=ABCMeta):
     @abstractmethod
     def on_Const(self, value):
-        pass
+        pass # :nocov:
 
     @abstractmethod
     def on_Signal(self, value):
-        pass
+        pass # :nocov:
 
     @abstractmethod
     def on_ClockSignal(self, value):
-        pass
+        pass # :nocov:
 
     @abstractmethod
     def on_ResetSignal(self, value):
-        pass
+        pass # :nocov:
 
     @abstractmethod
     def on_Operator(self, value):
-        pass
+        pass # :nocov:
 
     @abstractmethod
     def on_Slice(self, value):
-        pass
+        pass # :nocov:
 
     @abstractmethod
     def on_Part(self, value):
-        pass
+        pass # :nocov:
 
     @abstractmethod
     def on_Cat(self, value):
-        pass
+        pass # :nocov:
 
     @abstractmethod
     def on_Repl(self, value):
-        pass
+        pass # :nocov:
 
     @abstractmethod
     def on_ArrayProxy(self, value):
-        pass
+        pass # :nocov:
 
     def on_unknown_value(self, value):
         raise TypeError("Cannot transform value '{!r}'".format(value)) # :nocov:
@@ -126,15 +126,15 @@ class ValueTransformer(AbstractValueTransformer):
 class AbstractStatementTransformer(metaclass=ABCMeta):
     @abstractmethod
     def on_Assign(self, stmt):
-        pass
+        pass # :nocov:
 
     @abstractmethod
     def on_Switch(self, stmt):
-        pass
+        pass # :nocov:
 
     @abstractmethod
     def on_statements(self, stmt):
-        pass
+        pass # :nocov:
 
     def on_unknown_statement(self, stmt):
         raise TypeError("Cannot transform statement '{!r}'".format(stmt)) # :nocov:
@@ -173,6 +173,10 @@ class FragmentTransformer:
         for subfragment, name in fragment.subfragments:
             new_fragment.add_subfragment(self(subfragment), name)
 
+    def map_ports(self, fragment, new_fragment):
+        for port, dir in fragment.ports.items():
+            new_fragment.add_ports(port, dir=dir)
+
     def map_domains(self, fragment, new_fragment):
         for domain in fragment.iter_domains():
             new_fragment.add_domains(fragment.domains[domain])
@@ -189,6 +193,10 @@ class FragmentTransformer:
 
     def on_fragment(self, fragment):
         new_fragment = Fragment()
+        new_fragment.black_box = fragment.black_box
+        new_fragment.parameters = OrderedDict(fragment.parameters)
+        new_fragment.port_names = SignalDict(fragment.port_names.items())
+        self.map_ports(fragment, new_fragment)
         self.map_subfragments(fragment, new_fragment)
         self.map_domains(fragment, new_fragment)
         self.map_statements(fragment, new_fragment)
index dd39ac8be403b7c55ee86540414b962b8320213a..a90aac0a04d7363fee3c2b9d2e6b330ab5c68a2a 100644 (file)
@@ -1,3 +1,5 @@
+from collections import OrderedDict
+
 from ..hdl.ast import *
 from ..hdl.cd import *
 from ..hdl.ir import *
@@ -29,7 +31,7 @@ class FragmentPortsTestCase(FHDLTestCase):
 
     def test_iter_signals(self):
         f = Fragment()
-        f.add_ports(self.s1, self.s2, kind="io")
+        f.add_ports(self.s1, self.s2, dir="io")
         self.assertEqual(SignalSet((self.s1, self.s2)), f.iter_signals())
 
     def test_self_contained(self):
@@ -146,6 +148,18 @@ class FragmentPortsTestCase(FHDLTestCase):
             (sync.clk, "i"),
         ]))
 
+    def test_inout(self):
+        s = Signal()
+        f1 = Fragment()
+        f2 = Fragment()
+        f2.add_ports(s, dir="io")
+        f1.add_subfragment(f2)
+
+        f1._propagate_ports(ports=())
+        self.assertEqual(f1.ports, SignalDict([
+            (s, "io")
+        ]))
+
 
 class FragmentDomainsTestCase(FHDLTestCase):
     def test_iter_signals(self):
@@ -391,3 +405,18 @@ class FragmentDriverConflictTestCase(FHDLTestCase):
             (eq (sig c2) (const 1'd1))
         )
         """)
+
+
+class InstanceTestCase(FHDLTestCase):
+    def test_init(self):
+        rst = Signal()
+        stb = Signal()
+        pins = Signal(8)
+        inst = Instance("cpu", p_RESET=0x1234, i_rst=rst, o_stb=stb, io_pins=pins)
+        self.assertEqual(inst.black_box, "cpu")
+        self.assertEqual(inst.parameters, OrderedDict([("RESET", 0x1234)]))
+        self.assertEqual(inst.ports, SignalDict([
+            (rst, "i"),
+            (stb, "o"),
+            (pins, "io"),
+        ]))