diff --git a/include/circt/Dialect/ESI/ESIDialect.h b/include/circt/Dialect/ESI/ESIDialect.h index b1b953ece0f9..b6b60f03ab87 100644 --- a/include/circt/Dialect/ESI/ESIDialect.h +++ b/include/circt/Dialect/ESI/ESIDialect.h @@ -37,6 +37,10 @@ struct ESIPortValidReadyMapping { hw::PortInfo data, valid, ready; }; +/// Name of dialect attribute which governs whether or not to bundle (i.e. use +/// SystemVerilog interfaces) channel signal wires on external modules. +constexpr StringRef extModBundleSignalsAttrName = "esi.bundle"; + /// Find all the port triples on a module which fit the /// /_valid/_ready pattern. Ready must be the opposite /// direction of the other two. diff --git a/integration_test/ESI/cosim/basic.mlir b/integration_test/ESI/cosim/basic.mlir index 0d2a9b7c2482..a3268c35d0d8 100644 --- a/integration_test/ESI/cosim/basic.mlir +++ b/integration_test/ESI/cosim/basic.mlir @@ -9,8 +9,8 @@ // PY: rpc.testVectorSum(25) // PY: rpc.testCrypto(25) -hw.module.extern @IntAccNoBP(%clk: i1, %rst: i1, %ints: !esi.channel) -> (totalOut: !esi.channel) -hw.module.extern @IntArrSum(%clk: i1, %rst: i1, %arr: !esi.channel>) -> (totalOut: !esi.channel>) +hw.module.extern @IntAccNoBP(%clk: i1, %rst: i1, %ints: !esi.channel) -> (totalOut: !esi.channel) attributes {esi.bundle} +hw.module.extern @IntArrSum(%clk: i1, %rst: i1, %arr: !esi.channel>) -> (totalOut: !esi.channel>) attributes {esi.bundle} hw.module @ints(%clk: i1, %rst: i1) { %intsIn = esi.cosim %clk, %rst, %intsTotalBuffered, "TestEP" : !esi.channel -> !esi.channel @@ -31,7 +31,7 @@ hw.module @array(%clk: i1, %rst: i1) { !Config = !hw.struct> !cfgChan = !esi.channel -hw.module.extern @Encryptor(%clk: i1, %rst: i1, %in: !pktChan, %cfg: !cfgChan) -> (x: !pktChan) +hw.module.extern @Encryptor(%clk: i1, %rst: i1, %in: !pktChan, %cfg: !cfgChan) -> (x: !pktChan) attributes {esi.bundle} hw.module @structs(%clk:i1, %rst:i1) -> () { %compressedData = hw.instance "otpCryptor" @Encryptor(clk: %clk: i1, rst: %rst: i1, in: %inputData: !pktChan, cfg: %cfg: !cfgChan) -> (x: !pktChan) diff --git a/integration_test/ESI/system/basic.mlir b/integration_test/ESI/system/basic.mlir index 2f21b457317f..cb28bf58dcd2 100644 --- a/integration_test/ESI/system/basic.mlir +++ b/integration_test/ESI/system/basic.mlir @@ -3,8 +3,8 @@ // RUN: circt-opt %t1.mlir -export-verilog -verify-diagnostics -o t3.mlir > %t2.sv // RUN: circt-rtl-sim.py %t2.sv %BININC%/circt/Dialect/ESI/ESIPrimitives.sv %S/../supplements/integers.sv --cycles 150 | FileCheck %s -hw.module.extern @IntCountProd(%clk: i1, %rst: i1) -> (ints: !esi.channel) -hw.module.extern @IntAcc(%clk: i1, %rst: i1, %ints: !esi.channel) -> (totalOut: i32) +hw.module.extern @IntCountProd(%clk: i1, %rst: i1) -> (ints: !esi.channel) attributes {esi.bundle} +hw.module.extern @IntAcc(%clk: i1, %rst: i1, %ints: !esi.channel) -> (totalOut: i32) attributes {esi.bundle} hw.module @top(%clk: i1, %rst: i1) -> (totalOut: i32) { %intStream = hw.instance "prod" @IntCountProd(clk: %clk: i1, rst: %rst: i1) -> (ints: !esi.channel) %intStreamBuffered = esi.buffer %clk, %rst, %intStream {stages=2, name="intChan"} : i32 diff --git a/lib/Dialect/ESI/ESIPasses.cpp b/lib/Dialect/ESI/ESIPasses.cpp index 5ef98708113a..86f4956c9b02 100644 --- a/lib/Dialect/ESI/ESIPasses.cpp +++ b/lib/Dialect/ESI/ESIPasses.cpp @@ -367,8 +367,8 @@ struct ESIPortsPass : public LowerESIPortsBase { void runOnOperation() override; private: - bool updateFunc(HWModuleOp mod); - void updateInstance(HWModuleOp mod, InstanceOp inst); + bool updateFunc(HWMutableModuleLike mod); + void updateInstance(HWMutableModuleLike mod, InstanceOp inst); bool updateFunc(HWModuleExternOp mod); void updateInstance(HWModuleExternOp mod, InstanceOp inst); @@ -385,7 +385,8 @@ void ESIPortsPass::runOnOperation() { // Find all externmodules and try to modify them. Remember the modified ones. DenseMap externModsMutated; for (auto mod : top.getOps()) - if (updateFunc(mod)) + if (mod->hasAttrOfType(extModBundleSignalsAttrName) && + updateFunc(mod)) externModsMutated[mod.getName()] = mod; // Find all instances and update them. @@ -397,14 +398,14 @@ void ESIPortsPass::runOnOperation() { // Find all modules and try to modify them to have wires with valid/ready // semantics. Remember the modified ones. - DenseMap modsMutated; - for (auto mod : top.getOps()) + DenseMap modsMutated; + for (auto mod : top.getOps()) if (updateFunc(mod)) - modsMutated[mod.getName()] = mod; + modsMutated[FlatSymbolRefAttr::get(mod)] = mod; // Find all instances and update them. top.walk([&modsMutated, this](InstanceOp inst) { - auto mapIter = modsMutated.find(inst.getModuleName()); + auto mapIter = modsMutated.find(inst.getModuleNameAttr()); if (mapIter != modsMutated.end()) updateInstance(mapIter->second, inst); }); @@ -419,16 +420,20 @@ static StringAttr appendToRtlName(StringAttr base, StringRef suffix) { } /// Convert all input and output ChannelTypes into valid/ready wires. Try not to -/// change the order and materialize ops in reasonably intuitive locations. -bool ESIPortsPass::updateFunc(HWModuleOp mod) { - auto funcType = mod.getFunctionType(); +/// change the order and materialize ops in reasonably intuitive locations. Will +/// modify the body only if one exists. +bool ESIPortsPass::updateFunc(HWMutableModuleLike mod) { + Block *body = nullptr; + if (mod->getNumRegions() == 1 && mod->getRegion(0).getBlocks().size() == 1) + body = &mod->getRegion(0).front(); + // Build ops in the module. - ImplicitLocOpBuilder modBuilder(mod.getLoc(), mod.getBody()); + ImplicitLocOpBuilder modBuilder(mod.getLoc(), mod); Type i1 = modBuilder.getI1Type(); + if (body) + modBuilder.setInsertionPointToStart(body); - // Get information to be used later on. - hw::OutputOp outOp = cast(mod.getBodyBlock()->getTerminator()); - + ModulePortInfo ports = mod.getPorts(); bool updated = false; // Reconstruct the list of operand types, changing the type whenever an ESI @@ -438,125 +443,150 @@ bool ESIPortsPass::updateFunc(HWModuleOp mod) { // 'Ready' signals are outputs. Remember them for later when we deal with the // returns. - SmallVector, 8> newReadySignals; - SmallVector newArgNames; - SmallVector newArgLocs; + SmallVector, 8> newReadySignals; + SmallVector> inputsToInsert; + SmallVector inputsToErase; - for (size_t argNum = 0, blockArgNum = 0, e = mod.getNumArguments(); - argNum < e; ++argNum, ++blockArgNum) { - Type argTy = funcType.getInput(argNum); - auto argNameAttr = getModuleArgumentNameAttr(mod, argNum); - auto argLocAttr = getModuleArgumentLocAttr(mod, argNum); + for (size_t argNum = 0, e = ports.inputs.size(), blockArgNum = 0; argNum < e; + ++argNum, ++blockArgNum) { + PortInfo &port = ports.inputs[argNum]; - auto chanTy = argTy.dyn_cast(); - if (!chanTy) { + auto chanTy = port.type.dyn_cast(); + if (!chanTy) // If not ESI, pass through. - newArgTypes.push_back(argTy); - newArgNames.push_back(argNameAttr); - newArgLocs.push_back(argLocAttr); continue; - } + // When we find one, add a data and valid signal to the new args. - Value data; - newArgTypes.push_back(chanTy.getInner()); - newArgNames.push_back(argNameAttr); - newArgLocs.push_back(argLocAttr); - data = - mod.front().insertArgument(blockArgNum, chanTy.getInner(), argLocAttr); - ++blockArgNum; - - newArgTypes.push_back(i1); - newArgNames.push_back(appendToRtlName(argNameAttr, "_valid")); - newArgLocs.push_back(argLocAttr); - Value valid = mod.front().insertArgument(blockArgNum, i1, argLocAttr); - ++blockArgNum; - // Build the ESI wrap operation to translate the lowered signals to what - // they were. (A later pass takes care of eliminating the ESI ops.) - auto wrap = modBuilder.create(data, valid); - // Replace uses of the old ESI port argument with the new one from the wrap. - mod.front() - .getArgument(blockArgNum) - .replaceAllUsesWith(wrap.getChanOutput()); - // Delete the ESI port block argument. - mod.front().eraseArgument(blockArgNum); - --blockArgNum; - newReadySignals.emplace_back( - wrap.getReady(), appendToRtlName(argNameAttr, "_ready"), argLocAttr); + PortInfo dataPort = { + port.name, PortDirection::INPUT, chanTy.getInner(), ~0U, {}, port.loc}; + inputsToInsert.emplace_back(argNum, dataPort); + + PortInfo validPort = {appendToRtlName(port.name, "_valid"), + PortDirection::INPUT, + i1, + ~0U, + {}, + port.loc}; + inputsToInsert.emplace_back(argNum, validPort); + + Value data, valid; + if (body) { + valid = body->insertArgument(blockArgNum, i1, port.loc); + data = body->insertArgument(blockArgNum, chanTy.getInner(), port.loc); + blockArgNum += 2; + } + + // And add the corresponding output ready signal. + PortInfo readyPort = {appendToRtlName(port.name, "_ready"), + PortDirection::OUTPUT, + i1, + ~0U, + {}, + port.loc}; + + Value ready; + if (body) { + // Build the ESI wrap operation to translate the lowered signals to what + // they were. (A later pass takes care of eliminating the ESI ops.) + auto wrap = modBuilder.create(data, valid); + ready = wrap.getReady(); + // Replace uses of the old ESI port argument with the new one from the + // wrap. + body->getArgument(blockArgNum).replaceAllUsesWith(wrap.getChanOutput()); + // Delete the ESI port block argument. + body->eraseArgument(blockArgNum); + --blockArgNum; + } + newReadySignals.emplace_back(ready, readyPort); + + inputsToErase.push_back(argNum); updated = true; } // Iterate through the outputs, appending to all of the next three lists. // Lower the ESI ports. - SmallVector newResultTypes; + SmallVector> outputsToInsert; + SmallVector outputsToErase; SmallVector newOutputOperands; - SmallVector newResultNames; - SmallVector newResultLocs; + unsigned oldNumInputs = ports.inputs.size(); - modBuilder.setInsertionPointToEnd(mod.getBodyBlock()); - for (size_t resNum = 0, numRes = funcType.getNumResults(); resNum < numRes; + Operation *outOp = nullptr; + if (body) { + outOp = body->getTerminator(); + modBuilder.setInsertionPoint(outOp); + } + for (size_t resNum = 0, numRes = ports.outputs.size(); resNum < numRes; ++resNum) { - Type resTy = funcType.getResult(resNum); - auto chanTy = resTy.dyn_cast(); - Value oldOutputValue = outOp.getOperand(resNum); - auto oldResultName = getModuleResultNameAttr(mod, resNum); - auto oldResultLoc = getModuleResultLocAttr(mod, resNum); + PortInfo &port = ports.outputs[resNum]; + auto chanTy = port.type.dyn_cast(); + Value oldOutputValue; + if (outOp) + oldOutputValue = outOp->getOperand(resNum); if (!chanTy) { // If not ESI, pass through. - newResultTypes.push_back(resTy); - newResultNames.push_back(oldResultName); - newResultLocs.push_back(oldResultLoc); newOutputOperands.push_back(oldOutputValue); continue; } // Lower the output, adding ready signals directly to the arg list. - Value ready = mod.front().addArgument(i1, oldResultLoc); // Ready block arg. - auto unwrap = modBuilder.create(oldOutputValue, ready); - newOutputOperands.push_back(unwrap.getRawOutput()); - newResultTypes.push_back(chanTy.getInner()); // Raw data. - newResultNames.push_back(oldResultName); - newResultLocs.push_back(oldResultLoc); - - newResultTypes.push_back(i1); // Valid. - newOutputOperands.push_back(unwrap.getValid()); - newResultNames.push_back(appendToRtlName(oldResultName, "_valid")); - newResultLocs.push_back(oldResultLoc); - - newArgTypes.push_back(i1); // Ready func arg. - newArgNames.push_back(appendToRtlName(oldResultName, "_ready")); - newArgLocs.push_back(oldResultLoc); + Value ready; + if (body) { + ready = body->addArgument(i1, port.loc); // Ready block arg. + auto unwrap = + modBuilder.create(oldOutputValue, ready); + newOutputOperands.push_back(unwrap.getRawOutput()); + newOutputOperands.push_back(unwrap.getValid()); + } + // New outputs. + // When we find one, add a data and valid signal to the new args. + PortInfo dataPort = { + port.name, PortDirection::OUTPUT, chanTy.getInner(), ~0U, {}, port.loc}; + outputsToInsert.emplace_back(resNum, dataPort); + + PortInfo validPort = {appendToRtlName(port.name, "_valid"), + PortDirection::OUTPUT, + i1, + ~0U, + {}, + port.loc}; + outputsToInsert.emplace_back(resNum, validPort); + + // New 'ready' input port. + PortInfo readyPort = {appendToRtlName(port.name, "_ready"), + PortDirection::INPUT, + i1, + ~0U, + {}, + port.loc}; + inputsToInsert.emplace_back(oldNumInputs, readyPort); + + outputsToErase.push_back(resNum); updated = true; } // Append the ready list signals we remembered above. - for (auto [value, name, location] : newReadySignals) { - newResultTypes.push_back(i1); - newOutputOperands.push_back(value); - newResultNames.push_back(name); - newResultLocs.push_back(location); + unsigned oldNumOutputs = ports.outputs.size(); + for (auto [value, port] : newReadySignals) { + if (body) + newOutputOperands.push_back(value); + outputsToInsert.emplace_back(oldNumOutputs, port); } if (!updated) return false; - // A new output op is necessary. - outOp.erase(); - modBuilder.create(newOutputOperands); - - // Set the new types. - auto newFuncType = modBuilder.getFunctionType(newArgTypes, newResultTypes); - mod.setType(newFuncType); - setModuleArgumentNames(mod, newArgNames); - setModuleArgumentLocs(mod, newArgLocs); - setModuleResultNames(mod, newResultNames); - setModuleResultLocs(mod, newResultLocs); + // A new set of output values is needed. + if (outOp) + outOp->setOperands(newOutputOperands); + mod.modifyPorts(inputsToInsert, outputsToInsert, inputsToErase, + outputsToErase); return true; } /// Update an instance of an updated module by adding `esi.[un]wrap.vr` /// ops around the instance. Lowering or folding away `[un]wrap` ops is another /// pass. -void ESIPortsPass::updateInstance(HWModuleOp mod, InstanceOp inst) { +void ESIPortsPass::updateInstance(HWMutableModuleLike mod, InstanceOp inst) { ImplicitLocOpBuilder b(inst.getLoc(), inst); BackedgeBuilder beb(b, inst.getLoc()); Type i1 = b.getI1Type(); @@ -704,6 +734,7 @@ bool ESIPortsPass::updateFunc(HWModuleExternOp mod) { updated = true; } + mod->removeAttr(extModBundleSignalsAttrName); if (!updated) return false; diff --git a/test/Dialect/ESI/cosim_structs.mlir b/test/Dialect/ESI/cosim_structs.mlir index abbd387cc389..c9544be7fc12 100644 --- a/test/Dialect/ESI/cosim_structs.mlir +++ b/test/Dialect/ESI/cosim_structs.mlir @@ -17,6 +17,6 @@ hw.module @top(%clk:i1, %rst:i1) -> () { // CAPNP-NEXT: compressionLevel @1 :UInt8; // CAPNP-NEXT: blob @2 :List(UInt8); -// COSIM: hw.instance "encodeStruct{{.+}}Inst" @encodeStruct{{.+}}(clk: %clk: i1, valid: %[[#]]: i1, unencodedInput: %[[#]]: !hw.struct>) -> (encoded: !hw.array<448xi1>) -// COSIM: hw.instance "Compressor" @Cosim_Endpoint(clk: %clk: i1, rst: %rst: i1, {{.+}}, {{.+}}, {{.+}}) -> ({{.+}}) -// COSIM: hw.module @encode{{.+}}(%clk: i1, %valid: i1, %unencodedInput: !hw.struct>) -> (encoded: !hw.array<448xi1>) +// COSIM-DAG: hw.instance "encodeStruct{{.+}}Inst" @encodeStruct{{.+}}(clk: %clk: i1, valid: %{{.+}}: i1, unencodedInput: %{{.+}}: !hw.struct>) -> (encoded: !hw.array<448xi1>) +// COSIM-DAG: hw.instance "Compressor" @Cosim_Endpoint(clk: %clk: i1, rst: %rst: i1, {{.+}}, {{.+}}, {{.+}}) -> ({{.+}}) +// COSIM-DAG: hw.module @encode{{.+}}(%clk: i1, %valid: i1, %unencodedInput: !hw.struct>) -> (encoded: !hw.array<448xi1>) diff --git a/test/Dialect/ESI/lowering.mlir b/test/Dialect/ESI/lowering.mlir index 521ee845ad2f..f049cb9a9699 100644 --- a/test/Dialect/ESI/lowering.mlir +++ b/test/Dialect/ESI/lowering.mlir @@ -2,9 +2,9 @@ // RUN: circt-opt %s --lower-esi-ports -verify-diagnostics | circt-opt -verify-diagnostics | FileCheck --check-prefix=IFACE %s // RUN: circt-opt %s --lower-esi-to-physical --lower-esi-ports --lower-esi-to-hw -verify-diagnostics | circt-opt -verify-diagnostics | FileCheck --check-prefix=HW %s -hw.module.extern @Sender(%clk: i1) -> (x: !esi.channel, y: i8) -hw.module.extern @ArrSender() -> (x: !esi.channel>) -hw.module.extern @Reciever(%a: !esi.channel, %clk: i1) +hw.module.extern @Sender(%clk: i1) -> (x: !esi.channel, y: i8) attributes {esi.bundle} +hw.module.extern @ArrSender() -> (x: !esi.channel>) attributes {esi.bundle} +hw.module.extern @Reciever(%a: !esi.channel, %clk: i1) attributes {esi.bundle} hw.module.extern @i0SenderReceiver(%in: !esi.channel) -> (out: !esi.channel) // CHECK-LABEL: hw.module.extern @Sender(%clk: i1) -> (x: !esi.channel, y: i8) @@ -22,16 +22,10 @@ hw.module.extern @i0SenderReceiver(%in: !esi.channel) -> (out: !esi.channel< // IFACE-NEXT: sv.interface.signal @data : !hw.array<4xi64> // IFACE-NEXT: sv.interface.modport @sink (input @ready, output @valid, output @data) // IFACE-NEXT: sv.interface.modport @source (input @valid, input @data, output @ready) -// IFACE-LABEL: sv.interface @IValidReady_i0 { -// IFACE-NEXT: sv.interface.signal @valid : i1 -// IFACE-NEXT: sv.interface.signal @ready : i1 -// IFACE-NEXT: sv.interface.signal @data : i0 -// IFACE-NEXT: sv.interface.modport @sink (input @ready, output @valid, output @data) -// IFACE-NEXT: sv.interface.modport @source (input @valid, input @data, output @ready) // IFACE-LABEL: hw.module.extern @Sender(%clk: i1, %x: !sv.modport<@IValidReady_i4::@sink>) -> (y: i8) // IFACE-LABEL: hw.module.extern @ArrSender(%x: !sv.modport<@IValidReady_ArrayOf4xi64::@sink>) // IFACE-LABEL: hw.module.extern @Reciever(%a: !sv.modport<@IValidReady_i4::@source>, %clk: i1) -// IFACE-LABEL: hw.module.extern @i0SenderReceiver(%in: !sv.modport<@IValidReady_i0::@source>, %out: !sv.modport<@IValidReady_i0::@sink>) +// IFACE-LABEL: hw.module.extern @i0SenderReceiver(%in: i0, %in_valid: i1, %out_ready: i1) -> (out: i0, out_valid: i1, in_ready: i1) hw.module @test(%clk:i1, %rst:i1) { @@ -108,7 +102,7 @@ hw.module @twoChannelArgs(%clk: i1, %ints: !esi.channel, %foo: !esi.channel // HW: hw.output %true, %true : i1, i1 // IFACE: %i1ToHandshake_fork0FromArg0 = sv.interface.instance : !sv.interface<@IValidReady_i1> -hw.module.extern @handshake_fork_1ins_2outs_ctrl(%in0: !esi.channel, %clock: i1, %reset: i1) -> (out0: !esi.channel, out1: !esi.channel) +hw.module.extern @handshake_fork_1ins_2outs_ctrl(%in0: !esi.channel, %clock: i1, %reset: i1) -> (out0: !esi.channel, out1: !esi.channel) attributes {esi.bundle} hw.module @test_constant(%arg0: !esi.channel, %clock: i1, %reset: i1) -> (outCtrl: !esi.channel) { %handshake_fork0.out0, %handshake_fork0.out1 = hw.instance "handshake_fork0" @handshake_fork_1ins_2outs_ctrl(in0: %arg0: !esi.channel, clock: %clock: i1, reset: %reset: i1) -> (out0: !esi.channel, out1: !esi.channel) hw.output %handshake_fork0.out1 : !esi.channel