fhdl.ir: add black-box fragments, fragment parameters, and Instance.

This commit is contained in:
whitequark 2018-12-17 22:55:30 +00:00
parent de6c12af77
commit c7f9386eab
7 changed files with 139 additions and 28 deletions

26
examples/inst.py Normal file
View file

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

View file

@ -1,7 +1,7 @@
from .hdl.ast import Value, Const, Mux, Cat, Repl, Array, Signal, ClockSignal, ResetSignal from .hdl.ast import Value, Const, Mux, Cat, Repl, Array, Signal, ClockSignal, ResetSignal
from .hdl.dsl import Module from .hdl.dsl import Module
from .hdl.cd import ClockDomain from .hdl.cd import ClockDomain
from .hdl.ir import Fragment from .hdl.ir import Fragment, Instance
from .hdl.xfrm import ResetInserter, CEInserter from .hdl.xfrm import ResetInserter, CEInserter
from .lib.cdc import MultiReg from .lib.cdc import MultiReg

View file

@ -567,6 +567,16 @@ class _StatementCompiler(xfrm.AbstractStatementTransformer):
def convert_fragment(builder, fragment, name, top): 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: with builder.module(name or "anonymous", attrs={"top": 1} if top else {}) as module:
compiler_state = _ValueCompilerState(module) compiler_state = _ValueCompilerState(module)
rhs_compiler = _RHSValueCompiler(compiler_state) rhs_compiler = _RHSValueCompiler(compiler_state)
@ -601,7 +611,7 @@ def convert_fragment(builder, fragment, name, top):
module.cell(sub_name, name=sub_name, ports={ module.cell(sub_name, name=sub_name, ports={
port: compiler_state.resolve_curr(signal, prefix=sub_name) port: compiler_state.resolve_curr(signal, prefix=sub_name)
for port, signal in sub_port_map.items() for port, signal in sub_port_map.items()
}) }, params=subfragment.parameters)
with module.process() as process: with module.process() as process:
with process.case() as case: with process.case() as case:

View file

@ -887,11 +887,11 @@ class Passive(Statement):
class _MappedKeyCollection(metaclass=ABCMeta): class _MappedKeyCollection(metaclass=ABCMeta):
@abstractmethod @abstractmethod
def _map_key(self, key): def _map_key(self, key):
pass pass # :nocov:
@abstractmethod @abstractmethod
def _unmap_key(self, key): def _unmap_key(self, key):
pass pass # :nocov:
class _MappedKeyDict(MutableMapping, _MappedKeyCollection): class _MappedKeyDict(MutableMapping, _MappedKeyCollection):

View file

@ -6,7 +6,7 @@ from .ast import *
from .cd import * from .cd import *
__all__ = ["Fragment", "DriverConflict"] __all__ = ["Fragment", "Instance", "DriverConflict"]
class DriverConflict(UserWarning): class DriverConflict(UserWarning):
@ -15,19 +15,28 @@ class DriverConflict(UserWarning):
class Fragment: class Fragment:
def __init__(self): def __init__(self):
self.black_box = None
self.port_names = SignalDict()
self.parameters = OrderedDict()
self.ports = SignalDict() self.ports = SignalDict()
self.drivers = OrderedDict() self.drivers = OrderedDict()
self.statements = [] self.statements = []
self.domains = OrderedDict() self.domains = OrderedDict()
self.subfragments = [] self.subfragments = []
def add_ports(self, *ports, kind): def add_ports(self, *ports, dir):
assert kind in ("i", "o", "io") assert dir in ("i", "o", "io")
for port in flatten(ports): for port in flatten(ports):
self.ports[port] = kind self.ports[port] = dir
def iter_ports(self): def iter_ports(self, dir=None):
yield from self.ports.keys() 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): def add_driver(self, signal, domain=None):
if domain not in self.drivers: if domain not in self.drivers:
@ -78,6 +87,9 @@ class Fragment:
assert isinstance(subfragment, Fragment) assert isinstance(subfragment, Fragment)
self.subfragments.append((subfragment, name)) self.subfragments.append((subfragment, name))
def get_fragment(self, platform):
return self
def _resolve_driver_conflicts(self, hierarchy=("top",), mode="warn"): def _resolve_driver_conflicts(self, hierarchy=("top",), mode="warn"):
assert mode in ("silent", "warn", "error") assert mode in ("silent", "warn", "error")
@ -248,7 +260,7 @@ class Fragment:
for subfrag, name in self.subfragments: for subfrag, name in self.subfragments:
# Always ask subfragments to provide all signals we're using and signals we're asked # 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. # 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, # 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, # 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. # 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, # Refine the output port approximation: if a subfragment is driving a signal,
# and we're asked to provide it, we can provide it now. # and we're asked to provide it, we can provide it now.
outs |= ports & sub_outs 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. # We've computed the precise set of input and output ports.
self.add_ports(ins, kind="i") self.add_ports(ins, dir="i")
self.add_ports(outs, kind="o") 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): def prepare(self, ports=(), ensure_sync_exists=True):
from .xfrm import FragmentTransformer from .xfrm import FragmentTransformer
@ -274,3 +291,24 @@ class Fragment:
fragment = fragment._lower_domain_signals() fragment = fragment._lower_domain_signals()
fragment._propagate_ports(ports) fragment._propagate_ports(ports)
return fragment 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))

View file

@ -18,43 +18,43 @@ __all__ = ["AbstractValueTransformer", "ValueTransformer",
class AbstractValueTransformer(metaclass=ABCMeta): class AbstractValueTransformer(metaclass=ABCMeta):
@abstractmethod @abstractmethod
def on_Const(self, value): def on_Const(self, value):
pass pass # :nocov:
@abstractmethod @abstractmethod
def on_Signal(self, value): def on_Signal(self, value):
pass pass # :nocov:
@abstractmethod @abstractmethod
def on_ClockSignal(self, value): def on_ClockSignal(self, value):
pass pass # :nocov:
@abstractmethod @abstractmethod
def on_ResetSignal(self, value): def on_ResetSignal(self, value):
pass pass # :nocov:
@abstractmethod @abstractmethod
def on_Operator(self, value): def on_Operator(self, value):
pass pass # :nocov:
@abstractmethod @abstractmethod
def on_Slice(self, value): def on_Slice(self, value):
pass pass # :nocov:
@abstractmethod @abstractmethod
def on_Part(self, value): def on_Part(self, value):
pass pass # :nocov:
@abstractmethod @abstractmethod
def on_Cat(self, value): def on_Cat(self, value):
pass pass # :nocov:
@abstractmethod @abstractmethod
def on_Repl(self, value): def on_Repl(self, value):
pass pass # :nocov:
@abstractmethod @abstractmethod
def on_ArrayProxy(self, value): def on_ArrayProxy(self, value):
pass pass # :nocov:
def on_unknown_value(self, value): def on_unknown_value(self, value):
raise TypeError("Cannot transform value '{!r}'".format(value)) # :nocov: raise TypeError("Cannot transform value '{!r}'".format(value)) # :nocov:
@ -126,15 +126,15 @@ class ValueTransformer(AbstractValueTransformer):
class AbstractStatementTransformer(metaclass=ABCMeta): class AbstractStatementTransformer(metaclass=ABCMeta):
@abstractmethod @abstractmethod
def on_Assign(self, stmt): def on_Assign(self, stmt):
pass pass # :nocov:
@abstractmethod @abstractmethod
def on_Switch(self, stmt): def on_Switch(self, stmt):
pass pass # :nocov:
@abstractmethod @abstractmethod
def on_statements(self, stmt): def on_statements(self, stmt):
pass pass # :nocov:
def on_unknown_statement(self, stmt): def on_unknown_statement(self, stmt):
raise TypeError("Cannot transform statement '{!r}'".format(stmt)) # :nocov: raise TypeError("Cannot transform statement '{!r}'".format(stmt)) # :nocov:
@ -173,6 +173,10 @@ class FragmentTransformer:
for subfragment, name in fragment.subfragments: for subfragment, name in fragment.subfragments:
new_fragment.add_subfragment(self(subfragment), name) 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): def map_domains(self, fragment, new_fragment):
for domain in fragment.iter_domains(): for domain in fragment.iter_domains():
new_fragment.add_domains(fragment.domains[domain]) new_fragment.add_domains(fragment.domains[domain])
@ -189,6 +193,10 @@ class FragmentTransformer:
def on_fragment(self, fragment): def on_fragment(self, fragment):
new_fragment = 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_subfragments(fragment, new_fragment)
self.map_domains(fragment, new_fragment) self.map_domains(fragment, new_fragment)
self.map_statements(fragment, new_fragment) self.map_statements(fragment, new_fragment)

View file

@ -1,3 +1,5 @@
from collections import OrderedDict
from ..hdl.ast import * from ..hdl.ast import *
from ..hdl.cd import * from ..hdl.cd import *
from ..hdl.ir import * from ..hdl.ir import *
@ -29,7 +31,7 @@ class FragmentPortsTestCase(FHDLTestCase):
def test_iter_signals(self): def test_iter_signals(self):
f = Fragment() 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()) self.assertEqual(SignalSet((self.s1, self.s2)), f.iter_signals())
def test_self_contained(self): def test_self_contained(self):
@ -146,6 +148,18 @@ class FragmentPortsTestCase(FHDLTestCase):
(sync.clk, "i"), (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): class FragmentDomainsTestCase(FHDLTestCase):
def test_iter_signals(self): def test_iter_signals(self):
@ -391,3 +405,18 @@ class FragmentDriverConflictTestCase(FHDLTestCase):
(eq (sig c2) (const 1'd1)) (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"),
]))