diff --git a/frontends/PyCDE/integration_test/esi_test.py b/frontends/PyCDE/integration_test/esi_test.py index 48966add3e68..2c78c15a41c1 100644 --- a/frontends/PyCDE/integration_test/esi_test.py +++ b/frontends/PyCDE/integration_test/esi_test.py @@ -7,9 +7,10 @@ import pycde from pycde import (AppID, Clock, Module, Reset, modparams, generator) from pycde.bsp import cosim -from pycde.constructs import Wire -from pycde.esi import FuncService, MMIO +from pycde.constructs import Reg, Wire +from pycde.esi import FuncService, MMIO, MMIOReadWriteCmdType from pycde.types import (Bits, Channel, UInt) +from pycde.behavioral import If, Else, EndIf import sys @@ -48,7 +49,7 @@ def build(ports): 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_data = (address + add_amt).as_bits(64) response_chan, response_ready = Channel(Bits(64)).wrap( response_data, address_valid) @@ -58,6 +59,37 @@ def build(ports): return MMIOClient +class MMIOReadWriteClient(Module): + clk = Clock() + rst = Reset() + + @generator + def build(ports): + mmio_read_write_bundle = MMIO.read_write(appid=AppID("mmio_rw_client")) + + cmd_chan_wire = Wire(Channel(MMIOReadWriteCmdType)) + resp_ready_wire = Wire(Bits(1)) + cmd, cmd_valid = cmd_chan_wire.unwrap(resp_ready_wire) + + add_amt = Reg(UInt(64), + clk=ports.clk, + rst=ports.rst, + rst_value=0, + ce=cmd_valid & cmd.write & (cmd.offset == 0x8).as_bits()) + add_amt.assign(cmd.data.as_uint()) + with If(cmd.write): + response_data = Bits(64)(0) + with Else(): + response_data = (cmd.offset + add_amt).as_bits(64) + EndIf() + response_chan, response_ready = Channel(Bits(64)).wrap( + response_data, cmd_valid) + resp_ready_wire.assign(response_ready) + + cmd_chan = mmio_read_write_bundle.unpack(data=response_chan)['cmd'] + cmd_chan_wire.assign(cmd_chan) + + class Top(Module): clk = Clock() rst = Reset() @@ -67,6 +99,7 @@ def construct(ports): LoopbackInOutAdd7(clk=ports.clk, rst=ports.rst) for i in range(4, 18, 5): MMIOClient(i)() + MMIOReadWriteClient(clk=ports.clk, rst=ports.rst) if __name__ == "__main__": diff --git a/frontends/PyCDE/integration_test/test_software/esi_test.py b/frontends/PyCDE/integration_test/test_software/esi_test.py index b9377e43b747..f264cf923e06 100644 --- a/frontends/PyCDE/integration_test/test_software/esi_test.py +++ b/frontends/PyCDE/integration_test/test_software/esi_test.py @@ -37,6 +37,26 @@ def read_offset(mmio_offset: int, offset: int, add_amt: int): read_offset(mmio_client_14_offset, 0, 14) read_offset(mmio_client_14_offset, 13, 14) +################################################################################ +# MMIOReadWriteClient tests +################################################################################ + +mmio_rw_client_offset = 262144 + + +def read_offset_check(i: int, add_amt: int): + d = mmio.read(mmio_rw_client_offset + i) + if d == i + 9: + print(f"PASS: read_offset_check({mmio_rw_client_offset} + {i}: {d}") + else: + assert False, f": read_offset_check({mmio_rw_client_offset} + {i}: {d}" + + +mmio.write(mmio_rw_client_offset + 8, 9) +read_offset_check(0, 9) +read_offset_check(12, 9) +read_offset_check(0x1400, 9) + ################################################################################ # Manifest tests ################################################################################ diff --git a/frontends/PyCDE/src/pycde/bsp/common.py b/frontends/PyCDE/src/pycde/bsp/common.py index 5a03cd148acf..9373bee5738f 100644 --- a/frontends/PyCDE/src/pycde/bsp/common.py +++ b/frontends/PyCDE/src/pycde/bsp/common.py @@ -117,7 +117,7 @@ class ChannelMMIO(esi.ServiceImplementation): clk = Clock() rst = Input(Bits(1)) - read = Input(esi.MMIO.read.type) + cmd = Input(esi.MMIOReadWriteCmdType) # Amount of register space each client gets. This is a GIANT HACK and needs to # be replaced by parameterizable services. @@ -140,70 +140,75 @@ class ChannelMMIO(esi.ServiceImplementation): @generator def generate(ports, bundles: esi._ServiceGeneratorBundles): - read_table, write_table, manifest_loc = ChannelMMIO.build_table( - ports, bundles) - ChannelMMIO.build_read(ports, manifest_loc, read_table) - ChannelMMIO.build_write(ports, write_table) + table, manifest_loc = ChannelMMIO.build_table(bundles) + ChannelMMIO.build_read(ports, manifest_loc, table) return True @staticmethod - def build_table( - ports, bundles - ) -> Tuple[Dict[int, AssignableSignal], Dict[int, AssignableSignal], int]: + def build_table(bundles) -> Tuple[Dict[int, AssignableSignal], int]: """Build a table of read and write addresses to BundleSignals.""" offset = ChannelMMIO.initial_offset - read_table: Dict[int, AssignableSignal] = {} - write_table: Dict[int, AssignableSignal] = {} + table: Dict[int, AssignableSignal] = {} for bundle in bundles.to_client_reqs: if bundle.port == 'read': - read_table[offset] = bundle - bundle.add_record({"offset": offset}) + table[offset] = bundle + bundle.add_record({"offset": offset, "type": "ro"}) + offset += ChannelMMIO.RegisterSpace + elif bundle.port == 'read_write': + table[offset] = bundle + bundle.add_record({"offset": offset, "type": "rw"}) offset += ChannelMMIO.RegisterSpace else: assert False, "Unrecognized port name." manifest_loc = offset - return read_table, write_table, manifest_loc + return table, manifest_loc @staticmethod - def build_read(ports, manifest_loc: int, read_table: Dict[int, - AssignableSignal]): + def build_read(ports, manifest_loc: int, table: Dict[int, AssignableSignal]): """Builds the read side of the MMIO service.""" # 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 + table[0] = header_bundle_wire HeaderMMIO(manifest_loc)(clk=ports.clk, rst=ports.rst, read=header_bundle_wire) mani_bundle_wire = Wire(esi.MMIO.read.type) - read_table[manifest_loc] = mani_bundle_wire + table[manifest_loc] = mani_bundle_wire ESI_Manifest_ROM_Wrapper(clk=ports.clk, read=mani_bundle_wire) - # Unpack the read bundle. + # Unpack the cmd bundle. data_resp_channel = Wire(Channel(esi.MMIODataType)) counted_output = Wire(Channel(esi.MMIODataType)) - read_addr_channel = ports.read.unpack(data=counted_output)["offset"] + cmd_channel = ports.cmd.unpack(data=counted_output)["cmd"] counted_output.assign(data_resp_channel) # 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) + sel_bits, client_cmd_chan = ChannelMMIO.build_addr_read(cmd_channel) # Build the demux/mux and assign the results of each appropriately. - read_clients_clog2 = clog2(len(read_table)) - client_addr_channels = esi.ChannelDemux( + read_clients_clog2 = clog2(len(table)) + client_cmd_channels = esi.ChannelDemux( sel=sel_bits.pad_or_truncate(read_clients_clog2), - input=client_address_chan, - num_outs=len(read_table)) + input=client_cmd_chan, + num_outs=len(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]) + for (idx, offset) in enumerate(sorted(table.keys())): + bundle_wire = table[offset] + bundle_type = bundle_wire.type + if bundle_type == esi.MMIO.read.type: + offset = client_cmd_channels[idx].transform(lambda cmd: cmd.offset) + bundle, bundle_froms = esi.MMIO.read.type.pack(offset=offset) + elif bundle_type == esi.MMIO.read_write.type: + bundle, bundle_froms = esi.MMIO.read_write.type.pack( + cmd=client_cmd_channels[idx]) + else: + assert False, "Unrecognized bundle type." + bundle_wire.assign(bundle) 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) @@ -218,18 +223,21 @@ def build_addr_read( # change to support more flexibility in addressing. Not clear if what we're # doing now it sufficient or not. - addr_ready_wire = Wire(Bits(1)) - addr, addr_valid = read_addr_chan.unwrap(addr_ready_wire) - addr = addr.as_bits() + cmd_ready_wire = Wire(Bits(1)) + cmd, cmd_valid = read_addr_chan.unwrap(cmd_ready_wire) 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) + sel_bits.assign(cmd.offset.as_bits()[ChannelMMIO.RegisterSpaceBits:]) + client_cmd = NamedWire(esi.MMIOReadWriteCmdType, "client_cmd") + client_cmd.assign( + esi.MMIOReadWriteCmdType({ + "write": + cmd.write, + "offset": (cmd.offset.as_bits() & + Bits(32)(ChannelMMIO.AddressMask)).as_uint(), + "data": + cmd.data + })) + client_addr_chan, client_addr_ready = Channel( + esi.MMIOReadWriteCmdType).wrap(client_cmd, cmd_valid) + cmd_ready_wire.assign(client_addr_ready) return sel_bits, client_addr_chan - - def build_write(self, bundles): - # TODO: this. - pass diff --git a/frontends/PyCDE/src/pycde/bsp/cosim.py b/frontends/PyCDE/src/pycde/bsp/cosim.py index fa3eb07ada8a..62360afa7d83 100644 --- a/frontends/PyCDE/src/pycde/bsp/cosim.py +++ b/frontends/PyCDE/src/pycde/bsp/cosim.py @@ -38,13 +38,13 @@ class ESI_Cosim_UserTopWrapper(Module): def build(ports): user_module(clk=ports.clk, rst=ports.rst) - mmio_read = esi.FuncService.get_coerced(esi.AppID("__cosim_mmio_read"), - esi.MMIO.read.type) + mmio_read_write = esi.FuncService.get_coerced( + esi.AppID("__cosim_mmio_read_write"), esi.MMIO.read_write.type) ChannelMMIO(esi.MMIO, appid=esi.AppID("__cosim_mmio"), clk=ports.clk, rst=ports.rst, - read=mmio_read) + cmd=mmio_read_write) class ESI_Cosim_Top(Module): clk = Clock() diff --git a/frontends/PyCDE/src/pycde/esi.py b/frontends/PyCDE/src/pycde/esi.py index f6f3873f9dc7..170a7b4b8c8e 100644 --- a/frontends/PyCDE/src/pycde/esi.py +++ b/frontends/PyCDE/src/pycde/esi.py @@ -10,7 +10,7 @@ from .support import get_user_loc from .system import System from .types import (Bits, Bundle, BundledChannel, Channel, ChannelDirection, - Type, UInt, types, _FromCirctType) + StructType, Type, UInt, types, _FromCirctType) from .circt import ir from .circt.dialects import esi as raw_esi, hw, msft @@ -469,6 +469,11 @@ def param(name: str, type: Type = None): MMIODataType = Bits(64) +MMIOReadWriteCmdType = StructType([ + ("write", Bits(1)), + ("offset", UInt(32)), + ("data", MMIODataType), +]) @ServiceDecl @@ -482,6 +487,11 @@ class MMIO: BundledChannel("data", ChannelDirection.FROM, MMIODataType) ]) + read_write = Bundle([ + BundledChannel("cmd", ChannelDirection.TO, MMIOReadWriteCmdType), + BundledChannel("data", ChannelDirection.FROM, MMIODataType) + ]) + @staticmethod def _op(sym_name: ir.StringAttr): return raw_esi.MMIOServiceDeclOp(sym_name) diff --git a/lib/Dialect/ESI/ESIStdServices.cpp b/lib/Dialect/ESI/ESIStdServices.cpp index 7bb11e259a74..c5b4dba24742 100644 --- a/lib/Dialect/ESI/ESIStdServices.cpp +++ b/lib/Dialect/ESI/ESIStdServices.cpp @@ -108,14 +108,26 @@ void MMIOServiceDeclOp::getPortList(SmallVectorImpl &ports) { BundledChannel{StringAttr::get(ctxt, "data"), ChannelDirection::from, ChannelType::get(ctxt, IntegerType::get(ctxt, 64))}}, /*resettable=*/UnitAttr())}); - // Write only port. + // Read-write port. + auto cmdType = hw::StructType::get( + ctxt, { + hw::StructType::FieldInfo{StringAttr::get(ctxt, "write"), + IntegerType::get(ctxt, 1)}, + hw::StructType::FieldInfo{ + StringAttr::get(ctxt, "offset"), + IntegerType::get( + ctxt, 32, IntegerType::SignednessSemantics::Unsigned)}, + hw::StructType::FieldInfo{StringAttr::get(ctxt, "data"), + IntegerType::get(ctxt, 64)}, + }); ports.push_back(ServicePortInfo{ - hw::InnerRefAttr::get(getSymNameAttr(), StringAttr::get(ctxt, "write")), + hw::InnerRefAttr::get(getSymNameAttr(), + StringAttr::get(ctxt, "read_write")), ChannelBundleType::get( ctxt, - {BundledChannel{StringAttr::get(ctxt, "offset"), ChannelDirection::to, - ChannelType::get(ctxt, IntegerType::get(ctxt, 32))}, - BundledChannel{StringAttr::get(ctxt, "data"), ChannelDirection::to, + {BundledChannel{StringAttr::get(ctxt, "cmd"), ChannelDirection::to, + ChannelType::get(ctxt, cmdType)}, + BundledChannel{StringAttr::get(ctxt, "data"), ChannelDirection::from, ChannelType::get(ctxt, IntegerType::get(ctxt, 64))}}, /*resettable=*/UnitAttr())}); } diff --git a/lib/Dialect/ESI/runtime/cpp/lib/backends/Cosim.cpp b/lib/Dialect/ESI/runtime/cpp/lib/backends/Cosim.cpp index 2533f68213b8..86a4685fd58b 100644 --- a/lib/Dialect/ESI/runtime/cpp/lib/backends/Cosim.cpp +++ b/lib/Dialect/ESI/runtime/cpp/lib/backends/Cosim.cpp @@ -232,7 +232,7 @@ class ReadCosimChannelPort getType()->getID() + ", got " + desc.type()); if (desc.dir() != ChannelDesc::Direction::ChannelDesc_Direction_TO_CLIENT) throw std::runtime_error("Channel '" + name + - "' is not a to server channel"); + "' is not a to client channel"); assert(desc.name() == name); // Initiate a stream of messages from the server. @@ -349,35 +349,49 @@ class CosimMMIO : public MMIO { CosimMMIO(Context &ctxt, StubContainer *rpcClient) { // We have to locate the channels ourselves since this service might be used // to retrieve the manifest. - ChannelDesc readArg, readResp; - if (!rpcClient->getChannelDesc("__cosim_mmio_read.arg", readArg) || - !rpcClient->getChannelDesc("__cosim_mmio_read.result", readResp)) + ChannelDesc cmdArg, cmdResp; + if (!rpcClient->getChannelDesc("__cosim_mmio_read_write.arg", cmdArg) || + !rpcClient->getChannelDesc("__cosim_mmio_read_write.result", cmdResp)) throw std::runtime_error("Could not find MMIO channels"); - const esi::Type *i32Type = getType(ctxt, new UIntType(readArg.type(), 32)); - const esi::Type *i64Type = getType(ctxt, new UIntType(readResp.type(), 64)); + const esi::Type *i64Type = getType(ctxt, new UIntType(cmdResp.type(), 64)); + const esi::Type *cmdType = + getType(ctxt, new StructType(cmdArg.type(), + {{"write", new BitsType("i1", 1)}, + {"offset", new UIntType("ui32", 32)}, + {"data", new BitsType("i64", 64)}})); // Get ports, create the function, then connect to it. - readArgPort = std::make_unique( - rpcClient->stub.get(), readArg, i32Type, "__cosim_mmio_read.arg"); - readRespPort = std::make_unique( - rpcClient->stub.get(), readResp, i64Type, "__cosim_mmio_read.result"); - readMMIO.reset(FuncService::Function::get(AppID("__cosim_mmio_read"), - *readArgPort, *readRespPort)); - readMMIO->connect(); + cmdArgPort = std::make_unique( + rpcClient->stub.get(), cmdArg, cmdType, "__cosim_mmio_read_write.arg"); + cmdRespPort = std::make_unique( + rpcClient->stub.get(), cmdResp, i64Type, + "__cosim_mmio_read_write.result"); + cmdMMIO.reset(FuncService::Function::get(AppID("__cosim_mmio"), *cmdArgPort, + *cmdRespPort)); + cmdMMIO->connect(); } + struct MMIOCmd { + uint64_t data; + uint32_t offset; + bool write; + } __attribute__((packed)); + // Call the read function and wait for a response. uint64_t read(uint32_t addr) const override { - auto arg = MessageData::from(addr); - std::future result = readMMIO->call(arg); + MMIOCmd cmd{.offset = addr, .write = false}; + auto arg = MessageData::from(cmd); + std::future result = cmdMMIO->call(arg); result.wait(); return *result.get().as(); } void write(uint32_t addr, uint64_t data) override { - // TODO: this. - throw std::runtime_error("Cosim MMIO write not implemented"); + MMIOCmd cmd{.data = data, .offset = addr, .write = true}; + auto arg = MessageData::from(cmd); + std::future result = cmdMMIO->call(arg); + result.wait(); } private: @@ -389,9 +403,9 @@ class CosimMMIO : public MMIO { ctxt.registerType(type); return type; } - std::unique_ptr readArgPort; - std::unique_ptr readRespPort; - std::unique_ptr readMMIO; + std::unique_ptr cmdArgPort; + std::unique_ptr cmdRespPort; + std::unique_ptr cmdMMIO; }; class CosimHostMem : public HostMem { diff --git a/test/Dialect/ESI/services.mlir b/test/Dialect/ESI/services.mlir index bc432cf64f5b..719116eba911 100644 --- a/test/Dialect/ESI/services.mlir +++ b/test/Dialect/ESI/services.mlir @@ -206,17 +206,23 @@ hw.module @CallableAccel1(in %clk: !seq.clock, in %rst: i1) { esi.service.std.mmio @mmio !mmioReq = !esi.bundle<[!esi.channel to "offset", !esi.channel from "data"]> +!mmioRWReq = !esi.bundle<[!esi.channel> to "cmd", !esi.channel from "data"]> -// CONN-LABEL: hw.module @MMIOManifest(in %clk : !seq.clock, in %rst : i1, in %manifest : !esi.bundle<[!esi.channel to "offset", !esi.channel from "data"]>) { +// CONN-LABEL: hw.module @MMIOManifest(in %clk : !seq.clock, in %rst : i1, in %manifest : !esi.bundle<[!esi.channel to "offset", !esi.channel from "data"]>, in %manifestRW : !esi.bundle<[!esi.channel> to "cmd", !esi.channel 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 to "offset", !esi.channel 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 to "offset", !esi.channel from "data"]> +// CONN-NEXT: esi.manifest.req #esi.appid<"manifestRW">, <@mmio::@read_write> std "esi.service.std.mmio", !esi.bundle<[!esi.channel> to "cmd", !esi.channel 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 %valid = hw.constant 1 : i1 %data_ch, %ready = esi.wrap.vr %data, %valid : i64 %addr = esi.bundle.unpack %data_ch from %req : !mmioReq + + %reqRW = esi.service.req <@mmio::@read_write> (#esi.appid<"manifestRW">) : !mmioRWReq + %dataChannel, %dataChannelReady = esi.wrap.vr %data, %valid: i64 + %cmdChannel = esi.bundle.unpack %dataChannel from %reqRW : !mmioRWReq }