[PyCDE] ESI pure module entry (#4635)

Teach PyCDE about ESI pure modules.
This commit is contained in:
John Demme 2023-02-07 14:41:33 -08:00 committed by GitHub
parent f50b5c737f
commit 28c132d1cc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 89 additions and 4 deletions

View File

@ -2,8 +2,9 @@
# See https://llvm.org/LICENSE.txt for license information.
# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
from .common import Input, Output, InputChannel, OutputChannel, _PyProxy
from .module import Generator, Module, ModuleLikeBuilderBase
from .common import (Input, Output, InputChannel, OutputChannel, Clock,
_PyProxy, PortError)
from .module import Generator, Module, ModuleLikeBuilderBase, PortProxyBase
from .signals import ChannelSignal, Signal, _FromCirctValue
from .system import System
from .types import Channel, Type, types, _FromCirctType
@ -452,3 +453,66 @@ def _import_ram_decl(sys: "System", ram_op: raw_esi.RandomAccessMemoryDeclOp):
ram.symbol = ir.StringAttr.get(sym)
install(ram_op)
return ram
class PureModuleBuilder(ModuleLikeBuilderBase):
"""Defines how an ESI `PureModule` gets built."""
@property
def circt_mod(self):
from .system import System
sys: System = System.current()
ret = sys._op_cache.get_circt_mod(self)
if ret is None:
return sys._create_circt_mod(self)
return ret
def create_op(self, sys: System, symbol):
"""Callback for creating a ESIPureModule op."""
return raw_esi.ESIPureModuleOp(symbol, loc=self.loc, ip=sys._get_ip())
def scan_cls(self):
"""Scan the class for input/output ports and generators. (Most `ModuleLike`
will use these.) Store the results for later use."""
generators = {}
for attr_name, attr in self.cls_dct.items():
if attr_name.startswith("_"):
continue
if isinstance(attr, (Clock, Input, Output)):
raise PortError("ESI pure modules cannot have ports")
elif isinstance(attr, Generator):
generators[attr_name] = attr
self.generators = generators
def create_port_proxy(self):
"""Since pure ESI modules don't have any ports, this function is pretty
boring."""
proxy_attrs = {}
return type(self.modcls.__name__ + "Ports", (PortProxyBase,), proxy_attrs)
def add_external_port_accessors(self):
"""Since we don't have ports, do nothing."""
pass
def generate(self):
"""Fill in (generate) this module. Only supports a single generator
currently."""
if len(self.generators) != 1:
raise ValueError("Must have exactly one generator.")
g: Generator = list(self.generators.values())[0]
entry_block = self.circt_mod.add_entry_block()
ports = self.generator_port_proxy(None, self)
with self.GeneratorCtxt(self, ports, entry_block, g.loc):
g.gen_func(ports)
class PureModule(Module):
"""A pure ESI module has no ports and contains only instances of modules with
only ESI ports and connections between said instances. Use ESI services for
external communication."""
BuilderType = PureModuleBuilder

View File

@ -123,7 +123,8 @@ class PortProxyBase:
def __init__(self, block_args, builder):
self._block_args = block_args
self._output_values = [None] * len(builder.outputs)
if builder.outputs is not None:
self._output_values = [None] * len(builder.outputs)
self._builder = builder
def _get_input(self, idx):
@ -314,7 +315,7 @@ class ModuleLikeBuilderBase(_PyProxy):
self.loc = loc
self.clk = None
self.ports = ports
if len(builder.clocks) == 1:
if builder.clocks is not None and len(builder.clocks) == 1:
# Enter clock block implicitly if only one clock given.
clk_port = list(builder.clocks)[0]
self.clk = ClockSignal(ports._block_args[clk_port], ClockType())

View File

@ -94,6 +94,17 @@ class LoopbackInOutTop(Module):
loopback.assign(data_chan)
# CHECK-LABEL: esi.pure_module @LoopbackInOutPure {
# CHECK: [[r0:%.+]] = esi.service.req.to_client <@HostComms::@from_host>(["loopback_in"]) : !esi.channel<i16>
# CHECK: esi.service.req.to_server [[r0]] -> <@HostComms::@to_host>(["loopback"]) : !esi.channel<i16>
@unittestmodule(print=True)
class LoopbackInOutPure(esi.PureModule):
@generator
def construct(self):
HostComms.to_host(HostComms.from_host("loopback_in", types.i16), "loopback")
class MultiplexerService(esi.ServiceImplementation):
clk = Clock()
rst = Input(types.i1)

View File

@ -33,3 +33,12 @@ class RandomAccessMemoryDeclOp:
@property
def innerType(self):
return ir.TypeAttr(self.attributes["innerType"])
class ESIPureModuleOp:
def add_entry_block(self):
if len(self.body.blocks) > 0:
raise IndexError('The module already has an entry block')
self.body.blocks.append()
return self.body.blocks[0]