[ESI] MMIO read service implementation in PyCDE (#7306)

Implements a channel-based MMIO read service generator. Changes the offset type on the MMIO std service to `ui32` from `i32`.
This commit is contained in:
John Demme 2024-07-12 05:01:41 -07:00 committed by GitHub
parent cdfcd96664
commit 34263bd91f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 381 additions and 131 deletions

View File

@ -5,12 +5,11 @@
# RUN: esi-cosim.py -- %PYTHON% %S/test_software/esi_test.py cosim env
import pycde
from pycde import (AppID, Clock, Input, Module, generator)
from pycde import (AppID, Clock, Module, Reset, modparams, generator)
from pycde.bsp import cosim
from pycde.constructs import Wire
from pycde.esi import FuncService
from pycde.types import (Bits, Bundle, BundledChannel, Channel,
ChannelDirection, UInt)
from pycde.esi import FuncService, MMIO
from pycde.types import (Bits, Channel, UInt)
import sys
@ -33,13 +32,38 @@ class LoopbackInOutAdd7(Module):
loopback.assign(data_chan)
@modparams
def MMIOClient(add_amt: int):
class MMIOClient(Module):
"""A module which requests an MMIO address space and upon an MMIO read
request, returns the <address offset into its space> + add_amt."""
@generator
def build(ports):
mmio_read_bundle = MMIO.read(appid=AppID("mmio_client", add_amt))
address_chan_wire = Wire(Channel(UInt(32)))
address, address_valid = address_chan_wire.unwrap(1)
response_data = (address.as_uint() + add_amt).as_bits(64)
response_chan, response_ready = Channel(Bits(64)).wrap(
response_data, address_valid)
address_chan = mmio_read_bundle.unpack(data=response_chan)['offset']
address_chan_wire.assign(address_chan)
return MMIOClient
class Top(Module):
clk = Clock()
rst = Input(Bits(1))
rst = Reset()
@generator
def construct(ports):
LoopbackInOutAdd7()
for i in range(4, 18, 5):
MMIOClient(i)()
if __name__ == "__main__":

View File

@ -7,9 +7,40 @@ acc = esi.AcceleratorConnection(platform, sys.argv[2])
mmio = acc.get_service_mmio()
data = mmio.read(8)
print(f"mmio data@8: {data:X}")
assert data == 0x207D98E5E5100E51
################################################################################
# MMIOClient tests
################################################################################
def read_offset(mmio_offset: int, offset: int, add_amt: int):
data = mmio.read(mmio_offset + offset)
if data == add_amt + offset:
print(f"PASS: read_offset({mmio_offset}, {offset}, {add_amt}) -> {data}")
else:
assert False, f"read_offset({mmio_offset}, {offset}, {add_amt}) -> {data}"
# MMIO offset into mmio_client[9]. TODO: get this from the manifest. API coming.
mmio_client_9_offset = 131072
read_offset(mmio_client_9_offset, 0, 9)
read_offset(mmio_client_9_offset, 13, 9)
# MMIO offset into mmio_client[4].
mmio_client_4_offset = 65536
read_offset(mmio_client_4_offset, 0, 4)
read_offset(mmio_client_4_offset, 13, 4)
# MMIO offset into mmio_client[14].
mmio_client_14_offset = 196608
read_offset(mmio_client_14_offset, 0, 14)
read_offset(mmio_client_14_offset, 13, 14)
################################################################################
# Manifest tests
################################################################################
assert acc.sysinfo().esi_version() == 0
m = acc.manifest()
assert m.api_version == 0
@ -23,6 +54,10 @@ recv.connect()
send = d.ports[esi.AppID("loopback_add7")].write_port("arg")
send.connect()
################################################################################
# Loopback add 7 tests
################################################################################
data = 10234
send.write(data)
got_data = False

View File

@ -2,12 +2,13 @@
# See https://llvm.org/LICENSE.txt for license information.
# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
from ..common import Clock, Input, Output
from ..constructs import ControlReg, Mux, NamedWire, Wire
from ..common import Clock, Input, Output, Reset
from ..constructs import AssignableSignal, NamedWire, Wire
from .. import esi
from ..module import Module, generator
from ..signals import BundleSignal
from ..types import Array, Bits, Channel, ChannelDirection
from ..module import Module, generator, modparams
from ..signals import BitsSignal, ChannelSignal
from ..support import clog2
from ..types import Array, Bits, Channel, UInt
from typing import Dict, Tuple
@ -27,10 +28,71 @@ class ESI_Manifest_ROM(Module):
data = Output(Bits(64))
class ESI_Manifest_ROM_Wrapper(Module):
"""Wrap the manifest ROM with ESI bundle."""
clk = Clock()
read = Input(esi.MMIO.read.type)
@generator
def build(self):
data, data_valid = Wire(Bits(64)), Wire(Bits(1))
data_chan, data_ready = Channel(Bits(64)).wrap(data, data_valid)
address_chan = self.read.unpack(data=data_chan)['offset']
address, address_valid = address_chan.unwrap(data_ready)
address_words = address.as_bits(32)[3:] # Lop off the lower three bits.
rom = ESI_Manifest_ROM(clk=self.clk, address=address_words)
data.assign(rom.data)
data_valid.assign(address_valid.reg(self.clk, name="data_valid", cycles=2))
@modparams
def HeaderMMIO(manifest_loc: int) -> Module:
class HeaderMMIO(Module):
"""Construct the ESI header MMIO adhering to the MMIO layout specified in
the ChannelMMIO service implementation."""
clk = Clock()
rst = Reset()
read = Input(esi.MMIO.read.type)
@generator
def build(ports):
data_chan_wire = Wire(Channel(esi.MMIODataType))
input_bundles = ports.read.unpack(data=data_chan_wire)
address_chan = input_bundles['offset']
address_ready = Wire(Bits(1))
address, address_valid = address_chan.unwrap(address_ready)
address_words = address.as_bits()[3:] # Lop off the lower three bits.
# Layout the header as an array.
header = Array(Bits(64), 4)([0, MagicNumber, VersionNumber, manifest_loc])
header.name = "header"
header_response_valid = address_valid # Zero latency read.
# Select the approptiate header index.
header_out = header[address_words[:2]]
header_out.name = "header_out"
# Wrap the response.
data_chan, data_chan_ready = Channel(esi.MMIODataType).wrap(
header_out, header_response_valid)
data_chan_wire.assign(data_chan)
address_ready.assign(data_chan_ready)
return HeaderMMIO
class ChannelMMIO(esi.ServiceImplementation):
"""MMIO service implementation with an AXI-lite protocol. This assumes a 32
bit address bus. It also only supports 64-bit aligned accesses and just throws
away the lower three bits of address.
"""MMIO service implementation with MMIO bundle interfaces. Should be
relatively easy to adapt to physical interfaces by wrapping the wires to
channels then bundles. Allows the implementation to be shared and (hopefully)
platform independent.
Whether or not to support unaligned accesses is up to the clients. The header
and manifest do not support unaligned accesses and throw away the lower three
bits.
Only allows for one outstanding request at a time. If a client doesn't return
a response, the MMIO service will hang. TODO: add some kind of timeout.
@ -41,14 +103,15 @@ class ChannelMMIO(esi.ServiceImplementation):
- 0x12: ESI version number (0)
- 0x18: Location of the manifest ROM (absolute address)
- 0x100: Start of MMIO space for requests. Mapping is contained in the
manifest so can be dynamically queried.
- 0x10000: Start of MMIO space for requests. Mapping is contained in the
manifest so can be dynamically queried.
- addr(Manifest ROM) + 0: Size of compressed manifest
- addr(Manifest ROM) + 8: Start of compressed manifest
This layout _should_ be pretty standard, but different BSPs may have various
different restrictions.
different restrictions. Any BSP which uses this service implementation will
have this layout, possibly with an offset or address window.
"""
clk = Clock()
@ -56,123 +119,116 @@ class ChannelMMIO(esi.ServiceImplementation):
read = Input(esi.MMIO.read.type)
# Amount of register space each client gets. This is a GIANT HACK and needs to
# be replaced by parameterizable services.
# TODO: make the amount of register space each client gets a parameter.
# Supporting this will require more address decode logic.
#
# TODO: if the compressed manifest is larger than 'RegisterSpace', we won't be
# allocating enough address space. This should be fixed with the more complex
# address decode logic mentioned above.
#
# TODO: only supports one outstanding transaction at a time. This is NOT
# enforced or checked! Enforce this.
RegisterSpace = 0x10000
RegisterSpaceBits = RegisterSpace.bit_length() - 1
AddressMask = 0xFFFF
# Start at this address for assigning MMIO addresses to service requests.
initial_offset: int = 0x100
initial_offset: int = RegisterSpace
@generator
def generate(self, bundles: esi._ServiceGeneratorBundles):
def generate(ports, bundles: esi._ServiceGeneratorBundles):
read_table, write_table, manifest_loc = ChannelMMIO.build_table(
self, bundles)
ChannelMMIO.build_read(self, manifest_loc, read_table)
ChannelMMIO.build_write(self, write_table)
ports, bundles)
ChannelMMIO.build_read(ports, manifest_loc, read_table)
ChannelMMIO.build_write(ports, write_table)
return True
@staticmethod
def build_table(
self,
bundles) -> Tuple[Dict[int, BundleSignal], Dict[int, BundleSignal], int]:
ports, bundles
) -> Tuple[Dict[int, AssignableSignal], Dict[int, AssignableSignal], int]:
"""Build a table of read and write addresses to BundleSignals."""
offset = ChannelMMIO.initial_offset
read_table = {}
write_table = {}
read_table: Dict[int, AssignableSignal] = {}
write_table: Dict[int, AssignableSignal] = {}
for bundle in bundles.to_client_reqs:
if bundle.direction == ChannelDirection.Input:
if bundle.port == 'read':
read_table[offset] = bundle
offset += 8
elif bundle.direction == ChannelDirection.Output:
write_table[offset] = bundle
offset += 8
bundle.add_record({"offset": offset})
offset += ChannelMMIO.RegisterSpace
else:
assert False, "Unrecognized port name."
manifest_loc = 1 << offset.bit_length()
manifest_loc = offset
return read_table, write_table, manifest_loc
def build_read(self, manifest_loc: int, bundles):
@staticmethod
def build_read(ports, manifest_loc: int, read_table: Dict[int,
AssignableSignal]):
"""Builds the read side of the MMIO service."""
# Currently just exposes the header and manifest. Not any of the possible
# service requests.
# Instantiate the header and manifest ROM. Fill in the read_table with
# bundle wires to be assigned identically to the other MMIO clients.
header_bundle_wire = Wire(esi.MMIO.read.type)
read_table[0] = header_bundle_wire
HeaderMMIO(manifest_loc)(clk=ports.clk,
rst=ports.rst,
read=header_bundle_wire)
i64 = Bits(64)
i2 = Bits(2)
i1 = Bits(1)
mani_bundle_wire = Wire(esi.MMIO.read.type)
read_table[manifest_loc] = mani_bundle_wire
ESI_Manifest_ROM_Wrapper(clk=ports.clk, read=mani_bundle_wire)
read_data_channel = Wire(Channel(esi.MMIOReadDataResponse),
"resp_data_channel")
read_addr_channel = self.read.unpack(data=read_data_channel)["offset"]
arready = Wire(i1)
(araddr, arvalid) = read_addr_channel.unwrap(arready)
# Unpack the read bundle.
data_resp_channel = Wire(Channel(esi.MMIODataType))
counted_output = Wire(Channel(esi.MMIODataType))
read_addr_channel = ports.read.unpack(data=counted_output)["offset"]
counted_output.assign(data_resp_channel)
address_written = NamedWire(i1, "address_written")
response_written = NamedWire(i1, "response_written")
# Get the selection index and the address to hand off to the clients.
sel_bits, client_address_chan = ChannelMMIO.build_addr_read(
read_addr_channel)
# Only allow one outstanding request at a time. Don't clear it until the
# output has been transmitted. This way, we don't have to deal with
# backpressure.
req_outstanding = ControlReg(self.clk,
self.rst, [address_written],
[response_written],
name="req_outstanding")
arready.assign(~req_outstanding)
# Build the demux/mux and assign the results of each appropriately.
read_clients_clog2 = clog2(len(read_table))
client_addr_channels = esi.ChannelDemux(
sel=sel_bits.pad_or_truncate(read_clients_clog2),
input=client_address_chan,
num_outs=len(read_table))
client_data_channels = []
for (idx, offset) in enumerate(sorted(read_table.keys())):
bundle, bundle_froms = esi.MMIO.read.type.pack(
offset=client_addr_channels[idx])
client_data_channels.append(bundle_froms["data"])
read_table[offset].assign(bundle)
resp_channel = esi.ChannelMux(client_data_channels)
data_resp_channel.assign(resp_channel)
# Capture the address if a the bus transaction occured.
address_written.assign(arvalid & ~req_outstanding)
address = araddr.reg(self.clk, ce=address_written, name="address")
address_valid = address_written.reg(name="address_valid")
address_words = address[3:] # Lop off the lower three bits.
@staticmethod
def build_addr_read(
read_addr_chan: ChannelSignal) -> Tuple[BitsSignal, ChannelSignal]:
"""Build a channel for the address read request. Returns the index to select
the client and a channel for the masked address to be passed to the
clients."""
# Set up the output of the data response pipeline. `data_pipeline*` are to
# be connected below.
data_pipeline_valid = NamedWire(i1, "data_pipeline_valid")
data_pipeline = NamedWire(i64, "data_pipeline")
data_pipeline_rresp = NamedWire(i2, "data_pipeline_rresp")
data_out_valid = ControlReg(self.clk,
self.rst, [data_pipeline_valid],
[response_written],
name="data_out_valid")
rvalid = data_out_valid
rdata = data_pipeline.reg(self.clk,
self.rst,
ce=data_pipeline_valid,
name="data_pipeline_reg")
read_resp_ch, rready = Channel(esi.MMIOReadDataResponse).wrap(rdata, rvalid)
read_data_channel.assign(read_resp_ch)
# Clear the `req_outstanding` flag when the response has been transmitted.
response_written.assign(data_out_valid & rready)
# Decoding the selection bits is very simple as of now. This might need to
# change to support more flexibility in addressing. Not clear if what we're
# doing now it sufficient or not.
# Handle reads from the header (< 0x100).
header_upper = address_words[ChannelMMIO.initial_offset.bit_length() - 2:]
# Is the address in the header?
header_sel = (header_upper == header_upper.type(0))
header_sel.name = "header_sel"
# Layout the header as an array.
header = Array(Bits(64), 4)([0, MagicNumber, VersionNumber, manifest_loc])
header.name = "header"
header_response_valid = address_valid # Zero latency read.
header_out = header[address_words[:2]]
header_out.name = "header_out"
header_rresp = i2(0)
# Handle reads from the manifest.
rom_address = NamedWire(
(address_words.as_uint() - (manifest_loc >> 3)).as_bits(29),
"rom_address")
mani_rom = ESI_Manifest_ROM(clk=self.clk, address=rom_address)
mani_valid = address_valid.reg(
self.clk,
self.rst,
rst_value=i1(0),
cycles=2, # Two cycle read to match the ROM latency.
name="mani_valid_reg")
mani_rresp = i2(0)
mani_sel = (address.as_uint() >= manifest_loc).as_bits(1)
# Mux the output depending on whether or not the address is in the header.
sel = NamedWire(mani_sel, "sel")
data_mux_inputs = [header_out, mani_rom.data]
data_pipeline.assign(Mux(sel, *data_mux_inputs))
data_valid_mux_inputs = [header_response_valid, mani_valid]
data_pipeline_valid.assign(Mux(sel, *data_valid_mux_inputs))
rresp_mux_inputs = [header_rresp, mani_rresp]
data_pipeline_rresp.assign(Mux(sel, *rresp_mux_inputs))
addr_ready_wire = Wire(Bits(1))
addr, addr_valid = read_addr_chan.unwrap(addr_ready_wire)
addr = addr.as_bits()
sel_bits = NamedWire(Bits(32 - ChannelMMIO.RegisterSpaceBits), "sel_bits")
sel_bits.assign(addr[ChannelMMIO.RegisterSpaceBits:])
client_addr = NamedWire(Bits(32), "client_addr")
client_addr.assign(addr & Bits(32)(ChannelMMIO.AddressMask))
client_addr_chan, client_addr_ready = Channel(UInt(32)).wrap(
client_addr.as_uint(), addr_valid)
addr_ready_wire.assign(client_addr_ready)
return sel_bits, client_addr_chan
def build_write(self, bundles):
# TODO: this.

View File

@ -3,13 +3,14 @@
# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
from .common import (AppID, Input, Output, _PyProxy, PortError)
from .constructs import AssignableSignal
from .module import Generator, Module, ModuleLikeBuilderBase, PortProxyBase
from .signals import BundleSignal, ChannelSignal, Signal, _FromCirctValue
from .constructs import AssignableSignal, Mux, Wire
from .module import generator, Module, ModuleLikeBuilderBase, PortProxyBase
from .signals import (BitsSignal, BundleSignal, ChannelSignal, Signal,
_FromCirctValue)
from .support import get_user_loc
from .system import System
from .types import (Bits, Bundle, BundledChannel, ChannelDirection, Type, types,
_FromCirctType)
from .types import (Bits, Bundle, BundledChannel, Channel, ChannelDirection,
Type, UInt, types, _FromCirctType)
from .circt import ir
from .circt.dialects import esi as raw_esi, hw, msft
@ -467,16 +468,18 @@ class PureModule(Module):
esi.ESIPureModuleParamOp(name, type_attr)
MMIOReadDataResponse = Bits(64)
MMIODataType = Bits(64)
@ServiceDecl
class MMIO:
"""ESI standard service to request access to an MMIO region."""
"""ESI standard service to request access to an MMIO region.
For now, each client request gets a 1KB region of memory."""
read = Bundle([
BundledChannel("offset", ChannelDirection.TO, Bits(32)),
BundledChannel("data", ChannelDirection.FROM, MMIOReadDataResponse)
BundledChannel("offset", ChannelDirection.TO, UInt(32)),
BundledChannel("data", ChannelDirection.FROM, MMIODataType)
])
@staticmethod
@ -624,3 +627,120 @@ def package(sys: System):
circt_lib_dir = build_dir / "tools" / "circt" / "lib"
esi_lib_dir = circt_lib_dir / "Dialect" / "ESI"
# shutil.copy(esi_lib_dir / "ESIPrimitives.sv", sys.hw_output_dir)
def ChannelDemux2(data_type: Type):
class ChannelDemux2(Module):
"""Combinational 2-way channel demultiplexer for valid/ready signaling."""
sel = Input(Bits(1))
inp = Input(Channel(data_type))
output0 = Output(Channel(data_type))
output1 = Output(Channel(data_type))
@generator
def generate(ports) -> None:
input_ready = Wire(Bits(1))
input, input_valid = ports.inp.unwrap(input_ready)
output0 = input
output0_valid = input_valid & (ports.sel == Bits(1)(0))
output0_ch, output0_ready = Channel(data_type).wrap(
output0, output0_valid)
ports.output0 = output0_ch
output1 = input
output1_valid = input_valid & (ports.sel == Bits(1)(1))
output1_ch, output1_ready = Channel(data_type).wrap(
output1, output1_valid)
ports.output1 = output1_ch
input_ready.assign((output0_ready & (ports.sel == Bits(1)(0))) |
(output1_ready & (ports.sel == Bits(1)(1))))
return ChannelDemux2
def ChannelDemux(input: ChannelSignal, sel: BitsSignal,
num_outs: int) -> List[ChannelSignal]:
"""Build a demultiplexer of ESI channels. Ideally, this would be a
parameterized module with an array of output channels, but the current ESI
channel-port lowering doesn't deal with arrays of channels. Independent of the
signaling protocol."""
dmux2 = ChannelDemux2(input.type)
def build_tree(inter_input: ChannelSignal, inter_sel: BitsSignal,
inter_num_outs: int, path: str) -> List[ChannelSignal]:
"""Builds a binary tree of demuxes to demux the input channel."""
if inter_num_outs == 0:
return []
if inter_num_outs == 1:
return [inter_input]
demux2 = dmux2(sel=inter_sel[-1].as_bits(),
inp=inter_input,
instance_name=f"demux2_path{path}")
next_sel = inter_sel[:-1].as_bits()
tree0 = build_tree(demux2.output0, next_sel, (inter_num_outs + 1) // 2,
path + "0")
tree1 = build_tree(demux2.output1, next_sel, (inter_num_outs + 1) // 2,
path + "1")
return tree0 + tree1
return build_tree(input, sel, num_outs, "")
def ChannelMux2(data_type: Channel):
class ChannelMux2(Module):
"""2 channel arbiter with priority given to input0. Valid/ready only.
Combinational."""
# TODO: implement some fairness.
input0 = Input(data_type)
input1 = Input(data_type)
output_channel = Output(data_type)
@generator
def generate(ports):
input0_ready = Wire(Bits(1))
input0_data, input0_valid = ports.input0.unwrap(input0_ready)
input1_ready = Wire(Bits(1))
input1_data, input1_valid = ports.input1.unwrap(input1_ready)
output_idx = ~input0_valid
data_mux = Mux(output_idx, input0_data, input1_data)
valid_mux = Mux(output_idx, input0_valid, input1_valid)
output_channel, output_ready = data_type.wrap(data_mux, valid_mux)
ports.output_channel = output_channel
input0_ready.assign(output_ready & ~output_idx)
input1_ready.assign(output_ready & output_idx)
return ChannelMux2
def ChannelMux(input_channels: List[ChannelSignal]) -> ChannelSignal:
"""Build a channel multiplexer of ESI channels. Ideally, this would be a
parameterized module with an array of output channels, but the current ESI
channel-port lowering doesn't deal with arrays of channels. Independent of the
signaling protocol."""
assert len(input_channels) > 0
mux2 = ChannelMux2(input_channels[0].type)
def build_tree(inter_input_channels: List[ChannelSignal]) -> ChannelSignal:
assert len(inter_input_channels) > 0
if len(inter_input_channels) == 1:
return inter_input_channels[0]
if len(inter_input_channels) == 2:
m = mux2(input0=inter_input_channels[0], input1=inter_input_channels[1])
return m.output_channel
m0_out = build_tree(inter_input_channels[:len(inter_input_channels) // 2])
m1_out = build_tree(inter_input_channels[len(inter_input_channels) // 2:])
m = mux2(input0=m0_out, input1=m1_out)
return m.output_channel
return build_tree(input_channels)

View File

@ -4,6 +4,13 @@ from .circt import ir
import os
def clog2(x: int) -> int:
"""Return the ceiling log base 2 of x."""
if x == 0:
return 0
return (x - 1).bit_length()
# PyCDE needs a custom version of this to support python classes.
def _obj_to_attribute(obj) -> ir.Attribute:
"""Create an MLIR attribute from a Python object for a few common cases."""

View File

@ -7,7 +7,7 @@ from __future__ import annotations
from collections import OrderedDict
from functools import singledispatchmethod
from .support import get_user_loc
from .support import clog2, get_user_loc
from .circt import ir, support
from .circt.dialects import esi, hw, seq, sv
@ -84,7 +84,7 @@ class Type:
return self
@property
def bitwidth(self):
def bitwidth(self) -> int:
return hw.get_bitwidth(self._type)
@property
@ -311,6 +311,10 @@ class Array(Type):
def size(self):
return self._type.size
@property
def select_bits(self) -> int:
return clog2(self.size)
@property
def shape(self):
_shape = [self.size]

View File

@ -183,9 +183,9 @@ class RecvBundleTest(Module):
# CHECK-LABEL: hw.module @MMIOReq()
# CHECK-NEXT: %c0_i64 = hw.constant 0 : i64
# CHECK-NEXT: %false = hw.constant false
# CHECK-NEXT: [[B:%.+]] = esi.service.req <@MMIO::@read>(#esi.appid<"mmio_req">) : !esi.bundle<[!esi.channel<i32> to "offset", !esi.channel<i64> from "data"]>
# CHECK-NEXT: [[B:%.+]] = esi.service.req <@MMIO::@read>(#esi.appid<"mmio_req">) : !esi.bundle<[!esi.channel<ui32> to "offset", !esi.channel<i64> from "data"]>
# CHECK-NEXT: %chanOutput, %ready = esi.wrap.vr %c0_i64, %false : i64
# CHECK-NEXT: %offset = esi.bundle.unpack %chanOutput from [[B]] : !esi.bundle<[!esi.channel<i32> to "offset", !esi.channel<i64> from "data"]>
# CHECK-NEXT: %offset = esi.bundle.unpack %chanOutput from [[B]] : !esi.bundle<[!esi.channel<ui32> to "offset", !esi.channel<i64> from "data"]>
@unittestmodule(esi_sys=True)
class MMIOReq(Module):

View File

@ -59,7 +59,7 @@ s.package()
# TOP: output s_axi_control_BVALID,
# TOP: output [1:0] s_axi_control_BRESP
# TOP: __ESI_Manifest_ROM ESI_Manifest_ROM (
# TOP: ESI_Manifest_ROM_Wrapper ESI_Manifest_ROM_Wrapper (
# TOP: .clk (ap_clk),
# TOP: .address (rom_address),
# TOP: .data (_ESI_Manifest_ROM_data)

View File

@ -99,8 +99,12 @@ void MMIOServiceDeclOp::getPortList(SmallVectorImpl<ServicePortInfo> &ports) {
hw::InnerRefAttr::get(getSymNameAttr(), StringAttr::get(ctxt, "read")),
ChannelBundleType::get(
ctxt,
{BundledChannel{StringAttr::get(ctxt, "offset"), ChannelDirection::to,
ChannelType::get(ctxt, IntegerType::get(ctxt, 32))},
{BundledChannel{
StringAttr::get(ctxt, "offset"), ChannelDirection::to,
ChannelType::get(
ctxt,
IntegerType::get(
ctxt, 32, IntegerType::SignednessSemantics::Unsigned))},
BundledChannel{StringAttr::get(ctxt, "data"), ChannelDirection::from,
ChannelType::get(ctxt, IntegerType::get(ctxt, 64))}},
/*resettable=*/UnitAttr())});

View File

@ -205,14 +205,14 @@ hw.module @CallableAccel1(in %clk: !seq.clock, in %rst: i1) {
}
esi.service.std.mmio @mmio
!mmioReq = !esi.bundle<[!esi.channel<i32> to "offset", !esi.channel<i64> from "data"]>
!mmioReq = !esi.bundle<[!esi.channel<ui32> to "offset", !esi.channel<i64> from "data"]>
// CONN-LABEL: hw.module @MMIOManifest(in %clk : !seq.clock, in %rst : i1, in %manifest : !esi.bundle<[!esi.channel<i32> to "offset", !esi.channel<i64> from "data"]>) {
// CONN-LABEL: hw.module @MMIOManifest(in %clk : !seq.clock, in %rst : i1, in %manifest : !esi.bundle<[!esi.channel<ui32> to "offset", !esi.channel<i64> from "data"]>) {
// CONN-NEXT: %true = hw.constant true
// CONN-NEXT: %c0_i64 = hw.constant 0 : i64
// CONN-NEXT: esi.manifest.req #esi.appid<"manifest">, <@mmio::@read> std "esi.service.std.mmio", !esi.bundle<[!esi.channel<i32> to "offset", !esi.channel<i64> from "data"]>
// CONN-NEXT: esi.manifest.req #esi.appid<"manifest">, <@mmio::@read> std "esi.service.std.mmio", !esi.bundle<[!esi.channel<ui32> to "offset", !esi.channel<i64> from "data"]>
// CONN-NEXT: %chanOutput, %ready = esi.wrap.vr %c0_i64, %true : i64
// CONN-NEXT: %offset = esi.bundle.unpack %chanOutput from %manifest : !esi.bundle<[!esi.channel<i32> to "offset", !esi.channel<i64> from "data"]>
// CONN-NEXT: %offset = esi.bundle.unpack %chanOutput from %manifest : !esi.bundle<[!esi.channel<ui32> to "offset", !esi.channel<i64> from "data"]>
hw.module @MMIOManifest(in %clk: !seq.clock, in %rst: i1) {
%req = esi.service.req <@mmio::@read> (#esi.appid<"manifest">) : !mmioReq
%data = hw.constant 0 : i64