From f780b84ecf008b8e37a325465e0378ad8eda439d Mon Sep 17 00:00:00 2001 From: Hideto Ueno Date: Wed, 1 May 2024 08:42:33 -0700 Subject: [PATCH] [SV] Add sv.func, sv.func.call{.procedural}, sv.func.dpi.import --- include/circt/Conversion/ExportVerilog.h | 3 + include/circt/Conversion/Passes.td | 4 +- .../circt/Dialect/HW/ModuleImplementation.h | 10 + include/circt/Dialect/SV/SVDialect.td | 2 +- include/circt/Dialect/SV/SVStatements.td | 178 ++++++++++ include/circt/Dialect/SV/SVVisitors.h | 11 +- .../ExportVerilog/ExportVerilog.cpp | 209 +++++++++-- .../ExportVerilog/ExportVerilogInternals.h | 4 +- .../ExportVerilog/LegalizeNames.cpp | 37 +- .../ExportVerilog/PrepareForEmission.cpp | 71 +++- .../ExportVerilog/PruneZeroValuedLogic.cpp | 8 +- lib/Conversion/PassDetail.h | 1 + lib/Conversion/VerifToSV/VerifToSV.cpp | 1 + lib/Dialect/HW/HWOps.cpp | 4 +- lib/Dialect/HW/ModuleImplementation.cpp | 34 +- lib/Dialect/SV/SVOps.cpp | 327 ++++++++++++++++++ .../ExportVerilog/prepare-for-emission.mlir | 2 +- test/Conversion/ExportVerilog/sv-dialect.mlir | 92 +++++ test/Dialect/HW/locations.mlir | 24 ++ test/Dialect/SV/basic.mlir | 19 + test/Dialect/SV/errors.mlir | 42 +++ 21 files changed, 1028 insertions(+), 55 deletions(-) diff --git a/include/circt/Conversion/ExportVerilog.h b/include/circt/Conversion/ExportVerilog.h index 6aa7eb1fa607..f16a05e28e1e 100644 --- a/include/circt/Conversion/ExportVerilog.h +++ b/include/circt/Conversion/ExportVerilog.h @@ -17,6 +17,9 @@ #include "mlir/Pass/Pass.h" namespace circt { +namespace hw { +class HWModuleLike; +} // namespace hw std::unique_ptr createTestApplyLoweringOptionPass(llvm::StringRef options); diff --git a/include/circt/Conversion/Passes.td b/include/circt/Conversion/Passes.td index a37c448eb9e0..abb2c525cf8b 100644 --- a/include/circt/Conversion/Passes.td +++ b/include/circt/Conversion/Passes.td @@ -118,8 +118,8 @@ def HWLowerInstanceChoices : Pass<"hw-lower-instance-choices", ]; } -def PrepareForEmission : Pass<"prepare-for-emission", - "hw::HWModuleOp"> { +def PrepareForEmission : InterfacePass<"prepare-for-emission", + "hw::HWModuleLike"> { let summary = "Prepare IR for ExportVerilog"; let description = [{ This pass runs only PrepareForEmission. diff --git a/include/circt/Dialect/HW/ModuleImplementation.h b/include/circt/Dialect/HW/ModuleImplementation.h index 4ccbd565ffd2..6e758699134e 100644 --- a/include/circt/Dialect/HW/ModuleImplementation.h +++ b/include/circt/Dialect/HW/ModuleImplementation.h @@ -18,6 +18,7 @@ #include "circt/Dialect/HW/HWTypes.h" #include "circt/Support/LLVM.h" #include "mlir/IR/DialectImplementation.h" +#include "mlir/IR/OpImplementation.h" namespace circt { namespace hw { @@ -50,7 +51,16 @@ void printModuleSignature(OpAsmPrinter &p, Operation *op, ParseResult parseModuleSignature(OpAsmParser &parser, SmallVectorImpl &args, TypeAttr &modType); + +void printModuleSignatureNew(OpAsmPrinter &p, Region &body, + hw::ModuleType modType, + ArrayRef portAttrs, + ArrayRef locAttrs); void printModuleSignatureNew(OpAsmPrinter &p, HWModuleLike op); +void getAsmBlockArgumentNamesImpl(mlir::Region ®ion, + OpAsmSetValueNameFn setNameFn); + +SmallVector getAllPortLocsImpl(hw::ModuleType modType); } // namespace module_like_impl } // namespace hw diff --git a/include/circt/Dialect/SV/SVDialect.td b/include/circt/Dialect/SV/SVDialect.td index 1ad2f1470c65..041564099c87 100644 --- a/include/circt/Dialect/SV/SVDialect.td +++ b/include/circt/Dialect/SV/SVDialect.td @@ -22,7 +22,7 @@ def SVDialect : Dialect { This dialect defines the `sv` dialect, which represents various SystemVerilog-specific constructs in an AST-like representation. }]; - let dependentDialects = ["circt::comb::CombDialect"]; + let dependentDialects = ["circt::comb::CombDialect", "circt::hw::HWDialect"]; let useDefaultTypePrinterParser = 1; let useDefaultAttributePrinterParser = 1; diff --git a/include/circt/Dialect/SV/SVStatements.td b/include/circt/Dialect/SV/SVStatements.td index 429aea26852b..30cb9217c9bd 100644 --- a/include/circt/Dialect/SV/SVStatements.td +++ b/include/circt/Dialect/SV/SVStatements.td @@ -12,6 +12,9 @@ include "mlir/IR/EnumAttr.td" include "mlir/IR/OpAsmInterface.td" +include "mlir/Interfaces/FunctionInterfaces.td" +include "mlir/Interfaces/ControlFlowInterfaces.td" +include "circt/Dialect/Emit/EmitOpInterfaces.td" //===----------------------------------------------------------------------===// // Control flow like-operations @@ -879,3 +882,178 @@ def MacroDefOp : SVOp<"macro.def", MacroDeclOp getReferencedMacro(const hw::HWSymbolCache *cache); }]; } + + +//===----------------------------------------------------------------------===// +// Function/Call +//===----------------------------------------------------------------------===// + +def FuncDPIImportOp: SVOp<"func.dpi.import", + [DeclareOpInterfaceMethods]> { + let summary = "Emit a dpi import statement. See SV spec 35.4"; + + let arguments = (ins FlatSymbolRefAttr:$callee, + OptionalAttr: $linkage_name); + let results = (outs); + + let assemblyFormat = "(`linkage` $linkage_name^)? $callee attr-dict"; +} + +def FuncOp : SVOp<"func", + [IsolatedFromAbove, Symbol, OpAsmOpInterface, ProceduralRegion, + DeclareOpInterfaceMethods, + DeclareOpInterfaceMethods, + FunctionOpInterface, HasParent<"mlir::ModuleOp">]> { + let summary = "A System Verilog function"; + let description = [{ + `sv.func` reresents System Verilog function in SV spec 13.4. + Similar to HW module, it's allowed to mix the order of input + and output arguments. `sv.func` can be used for both function + declaration and definition, i.e. a function without a body + region is a declaration. + + In SV there are two representations for function results, + "output argument" and "return value". Currently an output argument + is considered as as a return value if it's is the last argument + and has a special attribute `sv.func.explicitly_returned`. + }]; + + let arguments = (ins + SymbolNameAttr:$sym_name, + TypeAttrOf:$module_type, + OptionalAttr:$per_argument_attrs, + OptionalAttr:$input_locs, // Null for actual definition. + OptionalAttr:$result_locs, + OptionalAttr:$verilogName + ); + + let results = (outs); + let regions = (region AnyRegion:$body); + + let hasCustomAssemblyFormat = 1; + let extraClassDeclaration = [{ + static mlir::StringRef getExplicitlyReturnedAttrName() { + return "sv.func.explicitly_returned"; + } + + mlir::FunctionType getFunctionType() { + return getModuleType().getFuncType(); + } + + void setFunctionTypeAttr(mlir::TypeAttr mlirType) { + setModuleType(cast(mlirType.getValue())); + } + + /// Returns the argument types of this function. + ArrayRef getArgumentTypes() { return getFunctionType().getInputs(); } + + /// Returns the result types of this function. + ArrayRef getResultTypes() { return getFunctionType().getResults(); } + + Type getExplicitlyReturnedType(); + + size_t getNumOutputs() { + return getResultTypes().size(); + } + + size_t getNumInputs() { + return getArgumentTypes().size(); + } + + ::mlir::Region *getCallableRegion() { return isExternal() ? nullptr : &getBody(); } + bool isDeclaration() { return isExternal(); } + + /// OpAsmInterface + void getAsmBlockArgumentNames(mlir::Region ®ion, + mlir::OpAsmSetValueNameFn setNameFn); + SmallVector getPortList(bool excludeExplicitReturn); + }]; + let extraClassDefinition = [{ + hw::ModuleType $cppClass::getHWModuleType() { + return getModuleType(); + } + void $cppClass::setHWModuleType(hw::ModuleType type) { + return setModuleType(type); + } + void $cppClass::setAllPortNames(llvm::ArrayRef) { + } + + size_t $cppClass::getNumPorts() { + auto modty = getHWModuleType(); + return modty.getNumPorts(); + } + + size_t $cppClass::getNumInputPorts() { + auto modty = getHWModuleType(); + return modty.getNumInputs(); + } + + size_t $cppClass::getNumOutputPorts() { + auto modty = getHWModuleType(); + return modty.getNumOutputs(); + } + + size_t $cppClass::getPortIdForInputId(size_t idx) { + auto modty = getHWModuleType(); + return modty.getPortIdForInputId(idx); + } + + size_t $cppClass::getPortIdForOutputId(size_t idx) { + auto modty = getHWModuleType(); + return modty.getPortIdForOutputId(idx); + } + }]; +} + +class SVFuncCallBase traits = []>: SVOp]> { + + let arguments = (ins FlatSymbolRefAttr:$callee, Variadic:$inputs); + let results = (outs Variadic); + + let assemblyFormat = + "$callee `(` $inputs `)` attr-dict `:` functional-type($inputs, results)"; + + let extraClassDeclaration = [{ + Value getExplicitlyReturnedValue(sv::FuncOp op); + + operand_range getArgOperands() { + return getInputs(); + } + MutableOperandRange getArgOperandsMutable() { + return getInputsMutable(); + } + mlir::CallInterfaceCallable getCallableForCallee() { + return (*this)->getAttrOfType("callee"); + } + + /// Set the callee for this operation. + void setCalleeFromCallable(mlir::CallInterfaceCallable callee) { + (*this)->setAttr(getCalleeAttrName(), callee.get()); + } + }]; +} + +def FuncCallProceduralOp : SVFuncCallBase<"func.call.procedural", [ProceduralOp]> { + let summary = "Function call in a procedural region"; +} + +def FuncCallOp : SVFuncCallBase<"func.call", [NonProceduralOp]> { + let summary = "Function call in a non-procedural region"; + let description = [{ + This op represents a function call in a non-procedural region. + A function call in a non-procedural region must have a return + value and no output argument. + }]; +} + +def ReturnOp : SVOp<"return", + [Pure, ReturnLike, Terminator, HasParent<"FuncOp">]> { + let summary = "Function return operation"; + + let arguments = (ins Variadic:$operands); + + let hasCustomAssemblyFormat = 1; + let assemblyFormat = "attr-dict ($operands^ `:` type($operands))?"; + let hasVerifier = 1; +} diff --git a/include/circt/Dialect/SV/SVVisitors.h b/include/circt/Dialect/SV/SVVisitors.h index 1764afcbc725..ccd8fa1a8177 100644 --- a/include/circt/Dialect/SV/SVVisitors.h +++ b/include/circt/Dialect/SV/SVVisitors.h @@ -39,11 +39,13 @@ class Visitor { AlwaysCombOp, AlwaysFFOp, InitialOp, CaseOp, // Other Statements. AssignOp, BPAssignOp, PAssignOp, ForceOp, ReleaseOp, AliasOp, - FWriteOp, SystemFunctionOp, VerbatimOp, + FWriteOp, SystemFunctionOp, VerbatimOp, FuncCallOp, + FuncCallProceduralOp, ReturnOp, // Type declarations. InterfaceOp, InterfaceSignalOp, InterfaceModportOp, InterfaceInstanceOp, GetModportOp, AssignInterfaceSignalOp, - ReadInterfaceSignalOp, MacroDeclOp, MacroDefOp, + ReadInterfaceSignalOp, MacroDeclOp, MacroDefOp, FuncDPIImportOp, + FuncOp, // Verification statements. AssertOp, AssumeOp, CoverOp, AssertConcurrentOp, AssumeConcurrentOp, CoverConcurrentOp, @@ -127,6 +129,9 @@ class Visitor { HANDLE(AliasOp, Unhandled); HANDLE(FWriteOp, Unhandled); HANDLE(SystemFunctionOp, Unhandled); + HANDLE(FuncCallProceduralOp, Unhandled); + HANDLE(FuncCallOp, Unhandled); + HANDLE(ReturnOp, Unhandled); HANDLE(VerbatimOp, Unhandled); // Type declarations. @@ -139,6 +144,8 @@ class Visitor { HANDLE(ReadInterfaceSignalOp, Unhandled); HANDLE(MacroDefOp, Unhandled); HANDLE(MacroDeclOp, Unhandled); + HANDLE(FuncDPIImportOp, Unhandled); + HANDLE(FuncOp, Unhandled); // Verification statements. HANDLE(AssertOp, Unhandled); diff --git a/lib/Conversion/ExportVerilog/ExportVerilog.cpp b/lib/Conversion/ExportVerilog/ExportVerilog.cpp index 81ad22bf3dc5..47c2b169b038 100644 --- a/lib/Conversion/ExportVerilog/ExportVerilog.cpp +++ b/lib/Conversion/ExportVerilog/ExportVerilog.cpp @@ -190,7 +190,7 @@ StringRef ExportVerilog::getSymOpName(Operation *symOp) { if (auto attr = symOp->getAttrOfType("hw.verilogName")) return attr.getValue(); return TypeSwitch(symOp) - .Case( + .Case( [](Operation *op) { return getVerilogModuleName(op); }) .Case([&](InterfaceOp op) { return getVerilogModuleNameAttr(op).getValue(); @@ -347,7 +347,8 @@ static bool hasStructType(Type type) { /// Return the word (e.g. "reg") in Verilog to declare the specified thing. static StringRef getVerilogDeclWord(Operation *op, - const LoweringOptions &options) { + const LoweringOptions &options, + bool stripAutomatic = false) { if (isa(op)) { // Check if the type stored in this register is a struct or array of // structs. In this case, according to spec section 6.8, the "reg" prefix @@ -391,7 +392,7 @@ static StringRef getVerilogDeclWord(Operation *op, // within a struct type definition (e.g. struct packed {logic foo;}). So we // should not emit extra 'logic'. bool hasStruct = hasStructType(op->getResult(0).getType()); - if (isProcedural) + if (isProcedural && !stripAutomatic) return hasStruct ? "automatic" : "automatic logic"; return hasStruct ? "" : "logic"; } @@ -399,6 +400,9 @@ static StringRef getVerilogDeclWord(Operation *op, if (!isProcedural) return "wire"; + if (stripAutomatic) + return hasStructType(op->getResult(0).getType()) ? "" : "logic"; + // "automatic" values aren't allowed in disallowLocalVariables mode. assert(!options.disallowLocalVariables && "automatic variables not allowed"); @@ -1404,7 +1408,7 @@ StringAttr ExportVerilog::inferStructuralNameForTemporary(Value expr) { // Module ports carry names! if (auto blockArg = dyn_cast(expr)) { - auto moduleOp = cast(blockArg.getOwner()->getParentOp()); + auto moduleOp = cast(blockArg.getOwner()->getParentOp()); StringRef name = getPortVerilogName(moduleOp, blockArg.getArgNumber()); result = StringAttr::get(expr.getContext(), name); @@ -3658,7 +3662,8 @@ void NameCollector::collectNames(Block &block) { // Instances have an instance name to recognize but we don't need to look // at the result values since wires used by instances should be traversed // anyway. - if (isa(op)) + if (isa(op)) continue; if (isa(op.getDialect())) continue; @@ -3760,6 +3765,7 @@ class StmtEmitter : public EmitterBase, LogicalResult visitSV(ReleaseOp op); LogicalResult visitSV(AliasOp op); LogicalResult visitSV(InterfaceInstanceOp op); + LogicalResult emitTerminator(Operation *op, const ModulePortInfo &ports); LogicalResult visitStmt(OutputOp op); LogicalResult visitStmt(InstanceOp op); @@ -3839,6 +3845,14 @@ class StmtEmitter : public EmitterBase, LogicalResult visitVerif(verif::AssumeOp op); LogicalResult visitVerif(verif::CoverOp op); + LogicalResult visitSV(FuncDPIImportOp op); + LogicalResult visitSV(FuncOp op); + template + LogicalResult emitFunctionCall(CallOp callOp); + LogicalResult visitSV(FuncCallProceduralOp op); + LogicalResult visitSV(FuncCallOp op); + LogicalResult visitSV(ReturnOp op); + public: ModuleEmitter &emitter; @@ -3849,6 +3863,7 @@ class StmtEmitter : public EmitterBase, size_t maxTypeWidth = 0; const LoweringOptions &options; + bool withinFunctionContext = false; }; } // end anonymous namespace @@ -3920,9 +3935,9 @@ StmtEmitter::emitAssignLike(Op op, PPExtString syntax, } LogicalResult StmtEmitter::visitSV(AssignOp op) { - // prepare assigns wires to instance outputs, but these are logically handled - // in the port binding list when outputing an instance. - if (dyn_cast_or_null(op.getSrc().getDefiningOp())) + // prepare assigns wires to instance outputs and function results, but these + // are logically handled in the port binding list when outputing an instance. + if (isa_and_nonnull(op.getSrc().getDefiningOp())) return success(); if (emitter.assignsInlined.count(op)) @@ -3935,6 +3950,9 @@ LogicalResult StmtEmitter::visitSV(AssignOp op) { } LogicalResult StmtEmitter::visitSV(BPAssignOp op) { + if (op.getSrc().getDefiningOp()) + return success(); + // If the assign is emitted into logic declaration, we must not emit again. if (emitter.assignsInlined.count(op)) return success(); @@ -4036,16 +4054,13 @@ LogicalResult StmtEmitter::visitSV(InterfaceInstanceOp op) { return success(); } -/// For OutputOp we put "assign" statements at the end of the Verilog module to -/// assign the module outputs to intermediate wires. -LogicalResult StmtEmitter::visitStmt(OutputOp op) { +LogicalResult StmtEmitter::emitTerminator(Operation *op, + const ModulePortInfo &ports) { SmallPtrSet ops; - auto parent = op->getParentOfType(); - size_t operandIndex = 0; - ModulePortInfo ports(parent.getPortList()); + bool isProcedural = op->getParentOp()->hasTrait(); for (PortInfo port : ports.getOutputs()) { - auto operand = op.getOperand(operandIndex); + auto operand = op->getOperand(operandIndex); // Outputs that are set by the output port of an instance are handled // directly when the instance is emitted. // Keep synced with countStatements() and visitStmt(InstanceOp). @@ -4064,8 +4079,9 @@ LogicalResult StmtEmitter::visitStmt(OutputOp op) { ps.scopedBox(isZeroBit ? PP::neverbox : PP::ibox2, [&]() { if (isZeroBit) ps << "// Zero width: "; - - ps << "assign" << PP::space; + if (!isProcedural) + ps << "assign" << PP::space; + // ps << PP::space; ps << PPExtString(port.getVerilogName()); ps << PP::space << "=" << PP::space; ps.scopedBox(PP::ibox0, [&]() { @@ -4088,6 +4104,14 @@ LogicalResult StmtEmitter::visitStmt(OutputOp op) { return success(); } +/// For OutputOp we put "assign" statements at the end of the Verilog module to +/// assign the module outputs to intermediate wires. +LogicalResult StmtEmitter::visitStmt(OutputOp op) { + auto parent = op->getParentOfType(); + ModulePortInfo ports(parent.getPortList()); + return emitTerminator(op, ports); +} + LogicalResult StmtEmitter::visitStmt(TypeScopeOp op) { startStatement(); auto typescopeDef = ("_TYPESCOPE_" + op.getSymName()).str(); @@ -4129,6 +4153,125 @@ LogicalResult StmtEmitter::visitStmt(TypedeclOp op) { return success(); } +template +LogicalResult StmtEmitter::emitFunctionCall(CallOpTy op) { + startStatement(); + + auto callee = + dyn_cast(state.symbolCache.getDefinition(op.getCalleeAttr())); + + SmallPtrSet ops; + ops.insert(op); + assert(callee); + + auto explicitReturn = op.getExplicitlyReturnedValue(callee); + if (explicitReturn) { + assert(explicitReturn.hasOneUse()); + if (op->getParentOp()->template hasTrait()) { + auto bpassignOp = cast(*explicitReturn.user_begin()); + emitExpression(bpassignOp.getDest(), ops); + } else { + auto assignOp = cast(*explicitReturn.user_begin()); + emitExpression(assignOp.getDest(), ops); + } + ps << PP::nbsp << "=" << PP::nbsp; + } + + auto arguments = callee.getPortList(true); + + ps << PPExtString(getSymOpName(callee)) << "("; + + bool needsComma = false; + auto printArg = [&](Value value) { + if (needsComma) + ps << "," << PP::space; + emitExpression(value, ops); + needsComma = true; + }; + + ps.scopedBox(PP::ibox0, [&] { + unsigned inputIndex = 0, outputIndex = 0; + for (auto arg : arguments) { + if (arg.dir == hw::ModulePort::Output) + printArg( + op.getResults()[outputIndex++].getUsers().begin()->getOperand(0)); + else + printArg(op.getInputs()[inputIndex++]); + } + }); + + ps << ");"; + emitLocationInfoAndNewLine(ops); + return success(); +} + +LogicalResult StmtEmitter::visitSV(FuncCallProceduralOp op) { + return emitFunctionCall(op); +} + +LogicalResult StmtEmitter::visitSV(FuncCallOp op) { + op->getParentOp()->dump(); + return emitFunctionCall(op); +} + +template +void emitFunctionSignature(ModuleEmitter &emitter, PPS &ps, FuncOp op, + bool isAutomatic = false) { + ps << "function" << PP::nbsp; + if (isAutomatic) + ps << "automatic" << PP::nbsp; + auto retType = op.getExplicitlyReturnedType(); + if (retType) { + ps.invokeWithStringOS([&](auto &os) { + emitter.printPackedType(retType, os, op->getLoc(), {}, false); + }); + } else + ps << "void"; + ps << PP::nbsp << PPExtString(getSymOpName(op)); + + emitter.emitPortList( + op, ModulePortInfo(op.getPortList(/*excludeExplicitReturn=*/true))); +} + +LogicalResult StmtEmitter::visitSV(FuncOp op) { + // Nothing to emit for a declaration. + if (op.isDeclaration()) + return success(); + + startStatement(); + // A function is moduled as an automatic function. + emitFunctionSignature(emitter, ps, op, /*isAutomatic=*/true); + withinFunctionContext = true; + emitStatementBlock(op.getBody().getBlocks().front()); + ps << PP::newline; + ps << "endfunction"; + ps << PP::newline; + withinFunctionContext = false; + return success(); +} + +LogicalResult StmtEmitter::visitSV(ReturnOp op) { + auto parent = op->getParentOfType(); + ModulePortInfo ports(parent.getPortList(false)); + return emitTerminator(op, ports); +} + +LogicalResult StmtEmitter::visitSV(FuncDPIImportOp importOp) { + startStatement(); + + ps << "import" << PP::nbsp << "\"DPI-C\"" << PP::nbsp; + if (auto linkageName = importOp.getLinkageName()) + ps << *linkageName << PP::nbsp << "=" << PP::nbsp; + auto op = + cast(state.symbolCache.getDefinition(importOp.getCalleeAttr())); + assert(op.isDeclaration() && "function must be a declaration"); + emitFunctionSignature(emitter, ps, op); + assert(state.pendingNewline); + ps << PP::newline; + + return success(); +} + LogicalResult StmtEmitter::visitSV(FWriteOp op) { if (hasSVAttributes(op)) emitError(op, "SV attributes emission is unimplemented for the op"); @@ -5437,7 +5580,7 @@ LogicalResult StmtEmitter::emitDeclaration(Operation *op) { // Emit the leading word, like 'wire', 'reg' or 'logic'. auto type = value.getType(); - auto word = getVerilogDeclWord(op, state.options); + auto word = getVerilogDeclWord(op, state.options, withinFunctionContext); auto isZeroBit = isZeroBitType(type); ps.scopedBox(isZeroBit ? PP::neverbox : PP::ibox2, [&]() { unsigned targetColumn = 0; @@ -6085,7 +6228,7 @@ void FileEmitter::emit(Block *block) { for (Operation &op : *block) { TypeSwitch(&op) .Case([&](auto op) { emitOp(op); }) - .Case( + .Case( [&](auto op) { ModuleEmitter(state).emitStatement(op); }) .Case([&](auto op) { ModuleEmitter(state).emitBind(op); }) .Case( @@ -6312,13 +6455,24 @@ void SharedEmitterState::gatherFiles(bool separateModules) { else rootFile.ops.push_back(info); }) - .Case([&](Operation *op) { + .Case( + [&](Operation *op) { + // Emit into a separate file using the specified file name or + // replicate the operation in each outputfile. + if (!attr) { + replicatedOps.push_back(op); + } else + separateFile(op, ""); + }) + .Case([&](auto op) { // Emit into a separate file using the specified file name or // replicate the operation in each outputfile. if (!attr) { replicatedOps.push_back(op); } else separateFile(op, ""); + + symbolCache.addDefinition(op.getSymNameAttr(), op); }) .Case([&](HWGeneratorSchemaOp schemaOp) { symbolCache.addDefinition(schemaOp.getNameAttr(), schemaOp); @@ -6438,7 +6592,9 @@ static void emitOperation(VerilogEmitterState &state, Operation *op) { }) .Case( [&](auto op) { FileEmitter(state).emit(op); }) - .Case( + .Case( + [&](auto op) { ModuleEmitter(state).emitStatement(op); }) + .Case( [&](auto op) { ModuleEmitter(state).emitStatement(op); }) .Default([&](auto *op) { state.encounteredError = true; @@ -6587,12 +6743,13 @@ LogicalResult circt::exportVerilog(ModuleOp module, llvm::raw_ostream &os) { LoweringOptions options(module); if (failed(lowerHWInstanceChoices(module))) return failure(); - SmallVector modulesToPrepare; - module.walk([&](HWModuleOp op) { modulesToPrepare.push_back(op); }); + SmallVector modulesToPrepare; + module.walk([&](HWModuleLike op) { modulesToPrepare.push_back(op); }); if (failed(failableParallelForEach( module->getContext(), modulesToPrepare, [&](auto op) { return prepareHWModule(op, options); }))) return failure(); + return exportVerilogImpl(module, os); } @@ -6605,7 +6762,7 @@ struct ExportVerilogPass : public ExportVerilogBase { mlir::OpPassManager preparePM("builtin.module"); preparePM.addPass(createLegalizeAnonEnumsPass()); preparePM.addPass(createHWLowerInstanceChoicesPass()); - auto &modulePM = preparePM.nest(); + auto &modulePM = preparePM.nestAny(); modulePM.addPass(createPrepareForEmissionPass()); if (failed(runPipeline(preparePM, getOperation()))) return signalPassFailure(); @@ -6764,8 +6921,8 @@ LogicalResult circt::exportSplitVerilog(ModuleOp module, StringRef dirname) { LoweringOptions options(module); if (failed(lowerHWInstanceChoices(module))) return failure(); - SmallVector modulesToPrepare; - module.walk([&](HWModuleOp op) { modulesToPrepare.push_back(op); }); + SmallVector modulesToPrepare; + module.walk([&](HWModuleLike op) { modulesToPrepare.push_back(op); }); if (failed(failableParallelForEach( module->getContext(), modulesToPrepare, [&](auto op) { return prepareHWModule(op, options); }))) diff --git a/lib/Conversion/ExportVerilog/ExportVerilogInternals.h b/lib/Conversion/ExportVerilog/ExportVerilogInternals.h index 1ec0a5186503..79ff53ced154 100644 --- a/lib/Conversion/ExportVerilog/ExportVerilogInternals.h +++ b/lib/Conversion/ExportVerilog/ExportVerilogInternals.h @@ -445,10 +445,10 @@ LogicalResult lowerHWInstanceChoices(mlir::ModuleOp module); /// For each module we emit, do a prepass over the structure, pre-lowering and /// otherwise rewriting operations we don't want to emit. LogicalResult prepareHWModule(Block &block, const LoweringOptions &options); -LogicalResult prepareHWModule(hw::HWModuleOp module, +LogicalResult prepareHWModule(hw::HWModuleLike module, const LoweringOptions &options); -void pruneZeroValuedLogic(hw::HWModuleOp module); +void pruneZeroValuedLogic(Operation *module); /// Rewrite module names and interfaces to not conflict with each other or with /// Verilog keywords. diff --git a/lib/Conversion/ExportVerilog/LegalizeNames.cpp b/lib/Conversion/ExportVerilog/LegalizeNames.cpp index 66319d527b0f..7198a9ef7256 100644 --- a/lib/Conversion/ExportVerilog/LegalizeNames.cpp +++ b/lib/Conversion/ExportVerilog/LegalizeNames.cpp @@ -109,6 +109,7 @@ class GlobalNameResolver { /// globalNameTable. void legalizeModuleNames(HWModuleOp module); void legalizeInterfaceNames(InterfaceOp interface); + void legalizeFunctionNames(FuncOp func); // Gathers prefixes of enum types by inspecting typescopes in the module. void gatherEnumPrefixes(mlir::ModuleOp topLevel); @@ -129,15 +130,17 @@ class GlobalNameResolver { } // namespace circt // This function legalizes local names in the given module. -static void legalizeModuleLocalNames(HWModuleOp module, +static void legalizeModuleLocalNames(HWModuleLike module, const LoweringOptions &options, const GlobalNameTable &globalNameTable) { // A resolver for a local name collison. NameCollisionResolver nameResolver(options); - // Register names used by parameters. - for (auto param : module.getParameters()) - nameResolver.insertUsedName(globalNameTable.getParameterVerilogName( - module, cast(param).getName())); + if (auto hwModule = dyn_cast(*module)) { + // Register names used by parameters. + for (auto param : hwModule.getParameters()) + nameResolver.insertUsedName(globalNameTable.getParameterVerilogName( + module, cast(param).getName())); + } auto *ctxt = module.getContext(); @@ -148,6 +151,11 @@ static void legalizeModuleLocalNames(HWModuleOp module, bool updated = false; for (auto [idx, port] : llvm::enumerate(ports)) { auto verilogName = port.attrs.get(verilogNameAttr); + if (port.attrs.get(FuncOp::getExplicitlyReturnedAttrName())) { + updated = true; + newNames[idx] = StringAttr::get(ctxt, getSymOpName(module)); + continue; + } if (verilogName) { auto newName = StringAttr::get( ctxt, nameResolver.getLegalName(cast(verilogName))); @@ -253,11 +261,16 @@ GlobalNameResolver::GlobalNameResolver(mlir::ModuleOp topLevel, legalizeInterfaceNames(interface); continue; } + + if (auto func = dyn_cast(op)) { + legalizeFunctionNames(func); + continue; + } } // Legalize names in HW modules parallelly. mlir::parallelForEach( - topLevel.getContext(), topLevel.getOps(), [&](auto module) { + topLevel.getContext(), topLevel.getOps(), [&](auto module) { legalizeModuleLocalNames(module, options, globalNameTable); }); @@ -323,6 +336,18 @@ void GlobalNameResolver::legalizeInterfaceNames(InterfaceOp interface) { } } +void GlobalNameResolver::legalizeFunctionNames(FuncOp func) { + MLIRContext *ctxt = func.getContext(); + if (auto verilogName = func.getVerilogName()) { + globalNameResolver.insertUsedName(*verilogName); + return; + } + auto newName = globalNameResolver.getLegalName(func.getName()); + if (newName != func.getName()) { + func.setVerilogName(StringAttr::get(ctxt, newName)); + } +} + //===----------------------------------------------------------------------===// // Public interface //===----------------------------------------------------------------------===// diff --git a/lib/Conversion/ExportVerilog/PrepareForEmission.cpp b/lib/Conversion/ExportVerilog/PrepareForEmission.cpp index 8fdd42feac12..fa50981de313 100644 --- a/lib/Conversion/ExportVerilog/PrepareForEmission.cpp +++ b/lib/Conversion/ExportVerilog/PrepareForEmission.cpp @@ -25,6 +25,7 @@ #include "circt/Dialect/LTL/LTLDialect.h" #include "circt/Dialect/Verif/VerifDialect.h" #include "mlir/IR/ImplicitLocOpBuilder.h" +#include "mlir/Interfaces/CallInterfaces.h" #include "llvm/ADT/DenseSet.h" #include "llvm/ADT/SmallPtrSet.h" #include "llvm/ADT/TypeSwitch.h" @@ -103,6 +104,38 @@ static void spillWiresForInstanceInputs(HWInstanceLike op) { } } +// Introduces a wire to replace an output port SSA wire. If the operation +// is in a procedural region, it creates a temporary logic, otherwise it +// places a wire. The connecting op is inserted in the op's region. +static void replacePortWithWire(ImplicitLocOpBuilder &builder, Operation *op, + Value result, StringRef name) { + + bool isProcedural = op->getParentOp()->hasTrait(); + + Value newTarget; + if (isProcedural) { + newTarget = builder.create(result.getType(), + builder.getStringAttr(name)); + } else { + newTarget = builder.create(result.getType(), name); + } + + while (!result.use_empty()) { + auto newRead = builder.create(newTarget); + OpOperand &use = *result.getUses().begin(); + use.set(newRead); + newRead->moveBefore(use.getOwner()); + } + + Operation *connect; + if (isProcedural) { + connect = builder.create(newTarget, result); + } else { + connect = builder.create(newTarget, result); + } + connect->moveAfter(op); +} + static StringAttr getResName(Operation *op, size_t idx) { if (auto inst = dyn_cast(op)) return inst.getResultName(idx); @@ -159,6 +192,33 @@ static void lowerInstanceResults(HWInstanceLike op) { } } +// Ensure that each output of a function call is used only by a wire or reg. +static void lowerFunctionCallResults(Operation *op) { + Block *block = op->getParentOfType().getBodyBlock(); + auto builder = ImplicitLocOpBuilder::atBlockBegin(op->getLoc(), block); + auto callee = op->getAttrOfType("callee"); + assert(callee); + SmallString<32> nameTmp{"_", callee.getValue(), "_"}; + + auto namePrefixSize = nameTmp.size(); + + for (auto [i, result] : llvm::enumerate(op->getResults())) { + if (result.hasOneUse()) { + Operation *user = *result.getUsers().begin(); + if (isa(user)) { + // Move assign op after instance to resolve cyclic dependencies. + user->moveAfter(op); + continue; + } + } + + nameTmp.resize(namePrefixSize); + // TODO: Use a result name as suffix. + nameTmp += std::to_string(i); + replacePortWithWire(builder, op, result, nameTmp); + } +} + /// Emit an explicit wire or logic to assign operation's result. This function /// is used to create a temporary to legalize a verilog expression or to /// resolve use-before-def in a graph region. If `emitWireAtBlockBegin` is true, @@ -906,6 +966,9 @@ static LogicalResult legalizeHWModule(Block &block, spillWiresForInstanceInputs(inst); } + if (auto call = dyn_cast(op)) + lowerFunctionCallResults(call); + // If logic op is located in a procedural region, we have to move the logic // op declaration to a valid program point. if (isProceduralRegion && isa(op)) { @@ -1247,8 +1310,12 @@ static LogicalResult legalizeHWModule(Block &block, } // NOLINTNEXTLINE(misc-no-recursion) -LogicalResult ExportVerilog::prepareHWModule(hw::HWModuleOp module, +LogicalResult ExportVerilog::prepareHWModule(hw::HWModuleLike module, const LoweringOptions &options) { + // Don't need to legalize modules without a body. + if (!module.getBodyBlock()) + return success(); + // Zero-valued logic pruning. pruneZeroValuedLogic(module); @@ -1267,7 +1334,7 @@ namespace { struct PrepareForEmissionPass : public PrepareForEmissionBase { void runOnOperation() override { - HWModuleOp module = getOperation(); + auto module = getOperation(); LoweringOptions options(cast(module->getParentOp())); if (failed(prepareHWModule(module, options))) signalPassFailure(); diff --git a/lib/Conversion/ExportVerilog/PruneZeroValuedLogic.cpp b/lib/Conversion/ExportVerilog/PruneZeroValuedLogic.cpp index 23960c8b9a58..81c076b8cd69 100644 --- a/lib/Conversion/ExportVerilog/PruneZeroValuedLogic.cpp +++ b/lib/Conversion/ExportVerilog/PruneZeroValuedLogic.cpp @@ -14,6 +14,7 @@ #include "circt/Dialect/Comb/CombOps.h" #include "circt/Dialect/HW/HWOps.h" #include "circt/Dialect/HW/HWPasses.h" +#include "circt/Dialect/SV/SVOps.h" #include "circt/Dialect/Seq/SeqOps.h" #include "mlir/IR/Builders.h" #include "mlir/IR/PatternMatch.h" @@ -241,9 +242,10 @@ static void addNoI0ResultPruningPattern(ConversionTarget &target, } // namespace -void ExportVerilog::pruneZeroValuedLogic(hw::HWModuleOp module) { - ConversionTarget target(*module.getContext()); - RewritePatternSet patterns(module.getContext()); +void ExportVerilog::pruneZeroValuedLogic(Operation *module) { + assert((isa(module))); + ConversionTarget target(*module->getContext()); + RewritePatternSet patterns(module->getContext()); PruneTypeConverter typeConverter; target.addLegalDialect(); diff --git a/lib/Conversion/PassDetail.h b/lib/Conversion/PassDetail.h index e92ed028afcc..e46655f97927 100644 --- a/lib/Conversion/PassDetail.h +++ b/lib/Conversion/PassDetail.h @@ -108,6 +108,7 @@ class EmitDialect; namespace hw { class HWDialect; class HWModuleOp; +class HWModuleLike; } // namespace hw namespace hwarith { diff --git a/lib/Conversion/VerifToSV/VerifToSV.cpp b/lib/Conversion/VerifToSV/VerifToSV.cpp index 3bc8d54acbd9..e6c48d41add2 100644 --- a/lib/Conversion/VerifToSV/VerifToSV.cpp +++ b/lib/Conversion/VerifToSV/VerifToSV.cpp @@ -13,6 +13,7 @@ #include "circt/Conversion/VerifToSV.h" #include "../PassDetail.h" #include "circt/Dialect/Comb/CombOps.h" +#include "circt/Dialect/HW/HWOpInterfaces.h" #include "circt/Dialect/HW/HWOps.h" #include "circt/Dialect/SV/SVOps.h" #include "circt/Dialect/Verif/VerifOps.h" diff --git a/lib/Dialect/HW/HWOps.cpp b/lib/Dialect/HW/HWOps.cpp index 50698a44d7d6..11cea5d099b2 100644 --- a/lib/Dialect/HW/HWOps.cpp +++ b/lib/Dialect/HW/HWOps.cpp @@ -1731,7 +1731,7 @@ LogicalResult OutputOp::verify() { auto modResults = modType.getOutputTypes(); OperandRange outputValues = getOperands(); if (modResults.size() != outputValues.size()) { - emitOpError("must have same number of operands as region results."); + emitOpError("must have same number of operands as region results"); return failure(); } @@ -1741,7 +1741,7 @@ LogicalResult OutputOp::verify() { emitOpError("output types must match module. In " "operand ") << i << ", expected " << modResults[i] << ", but got " - << outputValues[i].getType() << "."; + << outputValues[i].getType(); return failure(); } } diff --git a/lib/Dialect/HW/ModuleImplementation.cpp b/lib/Dialect/HW/ModuleImplementation.cpp index 0401309f3b75..11503ac8c2f6 100644 --- a/lib/Dialect/HW/ModuleImplementation.cpp +++ b/lib/Dialect/HW/ModuleImplementation.cpp @@ -391,20 +391,21 @@ static const char *directionAsString(ModulePort::Direction dir) { abort(); return "unknown"; } - void module_like_impl::printModuleSignatureNew(OpAsmPrinter &p, - HWModuleLike op) { + hw::HWModuleLike op) { + module_like_impl::printModuleSignatureNew( + p, op.getModuleBody(), op.getHWModuleType(), op.getAllPortAttrs(), + op.getAllPortLocs()); +} - Region &body = op.getModuleBody(); +void module_like_impl::printModuleSignatureNew(OpAsmPrinter &p, Region &body, + hw::ModuleType modType, + ArrayRef portAttrs, + ArrayRef locAttrs) { bool isExternal = body.empty(); SmallString<32> resultNameStr; mlir::OpPrintingFlags flags; unsigned curArg = 0; - - auto modType = op.getHWModuleType(); - auto portAttrs = op.getAllPortAttrs(); - auto locAttrs = op.getAllPortLocs(); - p << '('; for (auto [i, port] : llvm::enumerate(modType.getPorts())) { if (i > 0) @@ -449,3 +450,20 @@ void module_like_impl::printModuleSignatureNew(OpAsmPrinter &p, p << ')'; } + +/// Get a special name to use when printing the entry block arguments of the +/// region contained by an operation in this dialect. +void module_like_impl::getAsmBlockArgumentNamesImpl( + mlir::Region ®ion, OpAsmSetValueNameFn setNameFn) { + if (region.empty()) + return; + // Assign port names to the bbargs. + auto module = cast(region.getParentOp()); + + auto *block = ®ion.front(); + for (size_t i = 0, e = block->getNumArguments(); i != e; ++i) { + auto name = module.getInputName(i); + // Let mlir deterministically convert names to valid identifiers + setNameFn(block->getArgument(i), name); + } +} \ No newline at end of file diff --git a/lib/Dialect/SV/SVOps.cpp b/lib/Dialect/SV/SVOps.cpp index d0b2393a4e6e..a806e88b88cf 100644 --- a/lib/Dialect/SV/SVOps.cpp +++ b/lib/Dialect/SV/SVOps.cpp @@ -16,12 +16,14 @@ #include "circt/Dialect/HW/HWOps.h" #include "circt/Dialect/HW/HWSymCache.h" #include "circt/Dialect/HW/HWTypes.h" +#include "circt/Dialect/HW/ModuleImplementation.h" #include "circt/Dialect/SV/SVAttributes.h" #include "circt/Support/CustomDirectiveImpl.h" #include "mlir/IR/Builders.h" #include "mlir/IR/BuiltinTypes.h" #include "mlir/IR/Matchers.h" #include "mlir/IR/PatternMatch.h" +#include "mlir/Interfaces/FunctionImplementation.h" #include "llvm/ADT/SmallString.h" #include "llvm/ADT/StringExtras.h" #include "llvm/ADT/TypeSwitch.h" @@ -2106,6 +2108,331 @@ ModportStructAttr ModportStructAttr::get(MLIRContext *context, return get(context, ModportDirectionAttr::get(context, direction), signal); } +//===----------------------------------------------------------------------===// +// FuncOp +//===----------------------------------------------------------------------===// + +ParseResult FuncOp::parse(OpAsmParser &parser, OperationState &result) { + auto builder = parser.getBuilder(); + // Parse visibility. + (void)mlir::impl::parseOptionalVisibilityKeyword(parser, result.attributes); + + // Parse the name as a symbol. + StringAttr nameAttr; + if (parser.parseSymbolName(nameAttr, SymbolTable::getSymbolAttrName(), + result.attributes)) + return failure(); + + SmallVector ports; + TypeAttr modType; + if (failed( + hw::module_like_impl::parseModuleSignature(parser, ports, modType))) + return failure(); + + result.addAttribute(FuncOp::getModuleTypeAttrName(result.name), modType); + + // Convert the specified array of dictionary attrs (which may have null + // entries) to an ArrayAttr of dictionaries. + auto unknownLoc = builder.getUnknownLoc(); + SmallVector attrs, inputLocs, outputLocs; + auto nonEmptyLocsFn = [unknownLoc](Attribute attr) { + return attr && cast(attr) != unknownLoc; + }; + + for (auto &port : ports) { + attrs.push_back(port.attrs ? port.attrs : builder.getDictionaryAttr({})); + auto loc = port.sourceLoc ? Location(*port.sourceLoc) : unknownLoc; + (port.direction == hw::PortInfo::Direction::Output ? outputLocs : inputLocs) + .push_back(loc); + } + + result.addAttribute(FuncOp::getPerArgumentAttrsAttrName(result.name), + builder.getArrayAttr(attrs)); + + if (llvm::any_of(outputLocs, nonEmptyLocsFn)) + result.addAttribute(FuncOp::getResultLocsAttrName(result.name), + builder.getArrayAttr(outputLocs)); + // Parse the attribute dict. + if (failed(parser.parseOptionalAttrDictWithKeyword(result.attributes))) + return failure(); + + // Add the entry block arguments. + SmallVector entryArgs; + for (auto &port : ports) + if (port.direction != hw::ModulePort::Direction::Output) + entryArgs.push_back(port); + + // Parse the optional function body. The printer will not print the body if + // its empty, so disallow parsing of empty body in the parser. + auto *body = result.addRegion(); + llvm::SMLoc loc = parser.getCurrentLocation(); + + mlir::OptionalParseResult parseResult = + parser.parseOptionalRegion(*body, entryArgs, + /*enableNameShadowing=*/false); + if (parseResult.has_value()) { + if (failed(*parseResult)) + return failure(); + // Function body was parsed, make sure its not empty. + if (body->empty()) + return parser.emitError(loc, "expected non-empty function body"); + } else { + if (llvm::any_of(inputLocs, nonEmptyLocsFn)) + result.addAttribute(FuncOp::getInputLocsAttrName(result.name), + builder.getArrayAttr(inputLocs)); + } + + return success(); +} + +void FuncOp::getAsmBlockArgumentNames(mlir::Region ®ion, + mlir::OpAsmSetValueNameFn setNameFn) { + if (region.empty()) + return; + // Assign port names to the bbargs. + auto func = cast(region.getParentOp()); + + auto *block = ®ion.front(); + + auto names = func.getModuleType().getInputNames(); + for (size_t i = 0, e = block->getNumArguments(); i != e; ++i) { + // Let mlir deterministically convert names to valid identifiers + setNameFn(block->getArgument(i), cast(names[i])); + } +} + +Type FuncOp::getExplicitlyReturnedType() { + if (!getPerArgumentAttrs() || getNumOutputs() == 0) + return {}; + + // Check if the last port is used as an explicit return. + auto lastArgument = getModuleType().getPorts().back(); + auto lastArgumentAttr = dyn_cast( + getPerArgumentAttrsAttr()[getPerArgumentAttrsAttr().size() - 1]); + + if (lastArgument.dir == hw::ModulePort::Output && lastArgumentAttr && + lastArgumentAttr.getAs(getExplicitlyReturnedAttrName())) + return lastArgument.type; + return {}; +} + +ArrayRef FuncOp::getAllPortAttrs() { + if (getPerArgumentAttrs()) + return getPerArgumentAttrs()->getValue(); + return {}; +} + +void FuncOp::setAllPortAttrs(ArrayRef attrs) { + setPerArgumentAttrsAttr(ArrayAttr::get(getContext(), attrs)); +} + +void FuncOp::removeAllPortAttrs() { setPerArgumentAttrsAttr({}); } +SmallVector FuncOp::getAllPortLocs() { + SmallVector portLocs; + portLocs.reserve(getNumPorts()); + auto resultLocs = getResultLocsAttr(); + unsigned inputCount = 0; + auto modType = getModuleType(); + auto unknownLoc = UnknownLoc::get(getContext()); + auto *body = getBodyBlock(); + auto inputLocs = getInputLocsAttr(); + for (unsigned i = 0, e = getNumPorts(); i < e; ++i) { + if (modType.isOutput(i)) { + auto loc = resultLocs + ? cast( + resultLocs.getValue()[portLocs.size() - inputCount]) + : unknownLoc; + portLocs.push_back(loc); + } else { + auto loc = body ? body->getArgument(inputCount).getLoc() + : (inputLocs ? cast(inputLocs[inputCount]) + : unknownLoc); + portLocs.push_back(loc); + ++inputCount; + } + } + return portLocs; +} + +void FuncOp::setAllPortLocsAttrs(llvm::ArrayRef locs) { + SmallVector resultLocs, inputLocs; + unsigned inputCount = 0; + auto modType = getModuleType(); + auto *body = getBodyBlock(); + for (unsigned i = 0, e = getNumPorts(); i < e; ++i) { + if (modType.isOutput(i)) + resultLocs.push_back(locs[i]); + else if (body) + body->getArgument(inputCount++).setLoc(cast(locs[i])); + else // Need to store locations in an attribute if declaration. + inputLocs.push_back(locs[i]); + } + setResultLocsAttr(ArrayAttr::get(getContext(), resultLocs)); + if (!body) + setInputLocsAttr(ArrayAttr::get(getContext(), inputLocs)); +} + +SmallVector FuncOp::getPortList() { return getPortList(false); } + +hw::PortInfo FuncOp::getPort(size_t idx) { + auto modTy = getHWModuleType(); + auto emptyDict = DictionaryAttr::get(getContext()); + LocationAttr loc = getPortLoc(idx); + DictionaryAttr attrs = dyn_cast_or_null(getPortAttrs(idx)); + if (!attrs) + attrs = emptyDict; + return {modTy.getPorts()[idx], + modTy.isOutput(idx) ? modTy.getOutputIdForPortId(idx) + : modTy.getInputIdForPortId(idx), + attrs, loc}; +} + +SmallVector FuncOp::getPortList(bool excludeExplicitReturn) { + auto modTy = getModuleType(); + auto emptyDict = DictionaryAttr::get(getContext()); + auto skipLastArgument = getExplicitlyReturnedType() && excludeExplicitReturn; + SmallVector retval; + auto portAttr = getAllPortLocs(); + for (unsigned i = 0, e = skipLastArgument ? modTy.getNumPorts() - 1 + : modTy.getNumPorts(); + i < e; ++i) { + DictionaryAttr attrs = emptyDict; + if (auto perArgumentAttr = getPerArgumentAttrs()) + if (auto argumentAttr = + dyn_cast_or_null((*perArgumentAttr)[i])) + attrs = argumentAttr; + + retval.push_back({modTy.getPorts()[i], + modTy.isOutput(i) ? modTy.getOutputIdForPortId(i) + : modTy.getInputIdForPortId(i), + attrs, portAttr[i]}); + } + return retval; +} + +void FuncOp::print(OpAsmPrinter &p) { + FuncOp op = *this; + // Print the operation and the function name. + auto funcName = + op->getAttrOfType(SymbolTable::getSymbolAttrName()) + .getValue(); + p << ' '; + + StringRef visibilityAttrName = SymbolTable::getVisibilityAttrName(); + if (auto visibility = op->getAttrOfType(visibilityAttrName)) + p << visibility.getValue() << ' '; + p.printSymbolName(funcName); + hw::module_like_impl::printModuleSignatureNew( + p, op.getBody(), op.getModuleType(), + op.getPerArgumentAttrsAttr() + ? ArrayRef(op.getPerArgumentAttrsAttr().getValue()) + : ArrayRef{}, + getAllPortLocs()); + + mlir::function_interface_impl::printFunctionAttributes( + p, op, + {visibilityAttrName, getModuleTypeAttrName(), + getPerArgumentAttrsAttrName(), getInputLocsAttrName(), + getResultLocsAttrName()}); + // Print the body if this is not an external function. + Region &body = op->getRegion(0); + if (!body.empty()) { + p << ' '; + p.printRegion(body, /*printEntryBlockArgs=*/false, + /*printBlockTerminators=*/true); + } +} + +//===----------------------------------------------------------------------===// +// ReturnOp +//===----------------------------------------------------------------------===// + +LogicalResult ReturnOp::verify() { + auto func = getParentOp(); + auto funcResults = func.getResultTypes(); + auto returnedValues = getOperands(); + if (funcResults.size() != returnedValues.size()) + return emitOpError("must have same number of operands as region results."); + // Check that the types of our operands and the region's results match. + for (size_t i = 0, e = funcResults.size(); i < e; ++i) { + if (funcResults[i] != returnedValues[i].getType()) { + emitOpError("output types must match function. In " + "operand ") + << i << ", expected " << funcResults[i] << ", but got " + << returnedValues[i].getType() << "."; + return failure(); + } + } + return success(); +} + +//===----------------------------------------------------------------------===// +// Call Ops +//===----------------------------------------------------------------------===// + +static Value +getExplicitlyReturnedValueImpl(sv::FuncOp op, + mlir::Operation::result_range results) { + if (!op.getExplicitlyReturnedType()) + return {}; + return results.back(); +} + +Value FuncCallOp::getExplicitlyReturnedValue(sv::FuncOp op) { + return getExplicitlyReturnedValueImpl(op, getResults()); +} + +Value FuncCallProceduralOp::getExplicitlyReturnedValue(sv::FuncOp op) { + return getExplicitlyReturnedValueImpl(op, getResults()); +} + +LogicalResult +FuncCallProceduralOp::verifySymbolUses(SymbolTableCollection &symbolTable) { + auto referencedOp = dyn_cast_or_null( + symbolTable.lookupNearestSymbolFrom(*this, getCalleeAttr())); + if (!referencedOp) + return emitError("cannot find function declaration '") + << getCallee() << "'"; + return success(); +} + +LogicalResult FuncCallOp::verifySymbolUses(SymbolTableCollection &symbolTable) { + auto referencedOp = dyn_cast_or_null( + symbolTable.lookupNearestSymbolFrom(*this, getCalleeAttr())); + if (!referencedOp) + return emitError("cannot find function declaration '") + << getCallee() << "'"; + + // Non-procedural call cannot have output arguments. + if (referencedOp.getNumOutputs() != 1 || + !referencedOp.getExplicitlyReturnedType()) { + auto diag = emitError() + << "function called in a non-procedural region must " + "return a single result"; + diag.attachNote(referencedOp.getLoc()) << "doesn't satisfy the constraint"; + return failure(); + } + return success(); +} + +//===----------------------------------------------------------------------===// +// FuncDPIImportOp +//===----------------------------------------------------------------------===// + +LogicalResult +FuncDPIImportOp::verifySymbolUses(SymbolTableCollection &symbolTable) { + auto referencedOp = dyn_cast_or_null( + symbolTable.lookupNearestSymbolFrom(*this, getCalleeAttr())); + + if (!referencedOp) + return emitError("cannot find function declaration '") + << getCallee() << "'"; + if (!referencedOp.isDeclaration()) + return emitError("imported function must be a declaration but '") + << getCallee() << "' is defined"; + return success(); +} + //===----------------------------------------------------------------------===// // TableGen generated logic. //===----------------------------------------------------------------------===// diff --git a/test/Conversion/ExportVerilog/prepare-for-emission.mlir b/test/Conversion/ExportVerilog/prepare-for-emission.mlir index 17dd7d7aad4a..3bfa07c22b59 100644 --- a/test/Conversion/ExportVerilog/prepare-for-emission.mlir +++ b/test/Conversion/ExportVerilog/prepare-for-emission.mlir @@ -1,4 +1,4 @@ -// RUN: circt-opt %s -prepare-for-emission --split-input-file -verify-diagnostics | FileCheck %s +// RUN: circt-opt %s --pass-pipeline='builtin.module(any(prepare-for-emission))' --split-input-file -verify-diagnostics | FileCheck %s // RUN: circt-opt %s -export-verilog -split-input-file // CHECK: @namehint_variadic diff --git a/test/Conversion/ExportVerilog/sv-dialect.mlir b/test/Conversion/ExportVerilog/sv-dialect.mlir index 00da7d17d8ea..ebae0d0c177e 100644 --- a/test/Conversion/ExportVerilog/sv-dialect.mlir +++ b/test/Conversion/ExportVerilog/sv-dialect.mlir @@ -1662,6 +1662,98 @@ hw.module @IndexPartSelect(out a : i3) { hw.output %c : i3 } +// Functions. +sv.func private @function_declare1(in %in_0 : i2, out out_0: i2, in %in_1 : i2, out out_1 : i1) +sv.func private @function_declare2(in %in_0 : i2, in %in_1 : i2, out out_0 : i1 {sv.func.explicitly_returned}) +sv.func private @function_declare3(in %in_0 : i2, out out_0 : i2, out out_1 : i1 {sv.func.explicitly_returned}) + +// CHECK-LABEL: func_call +hw.module @func_call(in %in_0 : i2, in %in_1 : i2, in %in_2: i1, out out: i1) { + %fd = hw.constant 0x80000002 : i32 + // CHECK: wire _function_declare2_0; + // CHECK-NEXT: logic [1:0] _function_declare3_0; + // CHECK-NEXT: logic _function_declare3_1; + // CHECK-NEXT: logic [1:0] _function_declare1_0; + // CHECK-NEXT: logic _function_declare1_1; + // CHECK-NEXT: _function_declare2_0 = function_declare2(in_0, in_1); + %u1 = sv.func.call @function_declare2 (%in_0, %in_1) : (i2, i2) -> i1 + // CHECK: initial begin + // CHECK-NEXT: function_declare1(in_0, _function_declare1_0, in_1, _function_declare1_1); + // CHECK-NEXT: _function_declare3_1 = function_declare3(in_0, _function_declare3_0); + // CHECK-NEXT: $fwrite(32'h80000002, "%x %x %x %x\n", _function_declare1_0, _function_declare1_1, + // CHECK-NEXT: _function_declare3_0, _function_declare3_1); + // CHECK-NEXT: end + sv.initial { + %v1, %v2 = sv.func.call.procedural @function_declare1 (%in_0, %in_1) : (i2, i2) -> (i2, i1) + %v3, %v4 = sv.func.call.procedural @function_declare3 (%in_0, %in_1) : (i2, i2) -> (i2, i1) + sv.fwrite %fd, "%x %x %x %x\n"(%v1, %v2, %v3, %v4) : i2, i1, i2, i1 + } + hw.output %u1: i1 +} + +// CHECK-LABEL: function automatic logic func_def( +// CHECK-NEXT: input [1:0] in_0, +// CHECK-NEXT: output out_0, +// CHECK-NEXT: input [1:0] in_1 +// CHECK-NEXT: ); +// CHECK-EMPTY: +// CHECK-NEXT: logic [1:0] _GEN = 2'(in_0 + in_1); +// CHECK-NEXT: out_0 = _GEN[0]; +// CHECK-NEXT: func_def = _GEN[1]; +// CHECK-NEXT: endfunction +sv.func @func_def(in %in_0 : i2, out out_0: i1, in %in_1: i2, out out_1 : i1 {sv.func.explicitly_returned}) { + %add = comb.add %in_0, %in_1: i2 + %v0 = comb.extract %add from 0 : (i2) -> i1 + %v1 = comb.extract %add from 1 : (i2) -> i1 + sv.return %v0, %v1: i1, i1 +} + +// CHECK-LABEL: function automatic logic [31:0] recurse_add( +// CHECK-NEXT: input [31:0] n +// CHECK-NEXT: ); +// CHECK-EMPTY: +// CHECK-NEXT: logic [31:0] _recurse_add_0; +// CHECK-NEXT: logic [31:0] result; +// CHECK-NEXT: if (n <= 32'h1) +// CHECK-NEXT: result = n; +// CHECK-NEXT: else begin +// CHECK-NEXT: _recurse_add_0 = recurse_add(32'(n - 32'h1)); +// CHECK-NEXT: result = 32'(n + _recurse_add_0); +// CHECK-NEXT: end +// CHECK-NEXT: recurse_add = result +// CHECK-NEXT: endfunction +sv.func @recurse_add(in %n : i32, out out : i32 {sv.func.explicitly_returned}) { + %one = hw.constant 1 : i32 + %cond = comb.icmp bin ule %n, %one: i32 + %result = sv.logic : !hw.inout + sv.if %cond { + sv.bpassign %result, %n: i32 + } else { + %n1 = comb.sub %n, %one: i32 + %v = sv.func.call.procedural @recurse_add(%n1) : (i32) -> i32 + %add = comb.add %n, %v: i32 + sv.bpassign %result, %add: i32 + } + %read = sv.read_inout %result: !hw.inout + sv.return %read : i32 +} + +// Emit DPI import. + +// CHECK-LABEL: import "DPI-C" linkage_name = function void function_declare1( +// CHECK-NEXT: input [1:0] in_0, +// CHECK-NEXT: out_0, +// CHECK-NEXT: in_1, +// CHECK-NEXT: output out_1 +// CHECK-NEXT: ); +sv.func.dpi.import linkage "linkage_name" @function_declare1 + +// CHECK-LABEL: import "DPI-C" function logic function_declare2( +// CHECK-NEXT: input [1:0] in_0, +// CHECK-NEXT: in_1 +// CHECK-NEXT: ); +sv.func.dpi.import @function_declare2 + sv.macro.decl @FOO sv.macro.decl @BAR diff --git a/test/Dialect/HW/locations.mlir b/test/Dialect/HW/locations.mlir index 686ba3e94891..c1dadc247c53 100644 --- a/test/Dialect/HW/locations.mlir +++ b/test/Dialect/HW/locations.mlir @@ -12,6 +12,14 @@ hw.module.extern @test4(in %input: i7, out output: i7) hw.module.extern @test5(in %input: i7 {hw.arg = "arg"}, out output: i7 {hw.res = "res"}) hw.module.extern @test6(in %input: i7 loc("arg"), out output: i7 loc("res")) hw.module.extern @test7(in %input: i7 {hw.arg = "arg"} loc("arg"), out output: i7 {hw.res = "res"} loc("res")) +sv.func @test8(in %input: i7, out output: i7) { sv.return %input : i7 } +sv.func @test9(in %input: i7 {hw.arg = "arg"}, out output: i7 {hw.res = "res"}) { sv.return %input : i7 } +sv.func @test10(in %input: i7 loc("arg"), out output: i7 loc("res")) { sv.return %input : i7 } +sv.func @test11(in %input: i7 {hw.arg = "arg"} loc("arg"), out output: i7 {hw.res = "res"} loc("res")) { sv.return %input : i7 } +sv.func private @test12(in %input: i7, out output: i7) +sv.func private @test13(in %input: i7 {hw.arg = "arg"}, out output: i7 {hw.res = "res"}) +sv.func private @test14(in %input: i7 loc("arg"), out output: i7 loc("res")) +sv.func private @test15(in %input: i7 {hw.arg = "arg"} loc("arg"), out output: i7 {hw.res = "res"} loc("res")) // BASIC: hw.module @test0(in %input : i7, out output : i7) // BASIC: hw.module @test1(in %input : i7 {hw.arg = "arg"}, out output : i7 {hw.res = "res"}) @@ -21,6 +29,14 @@ hw.module.extern @test7(in %input: i7 {hw.arg = "arg"} loc("arg"), out output: i // BASIC: hw.module.extern @test5(in %input : i7 {hw.arg = "arg"}, out output : i7 {hw.res = "res"}) // BASIC: hw.module.extern @test6(in %input : i7, out output : i7) // BASIC: hw.module.extern @test7(in %input : i7 {hw.arg = "arg"}, out output : i7 {hw.res = "res"}) +// BASIC: sv.func @test8(in %input : i7, out output : i7) +// BASIC: sv.func @test9(in %input : i7 {hw.arg = "arg"}, out output : i7 {hw.res = "res"}) +// BASIC: sv.func @test10(in %input : i7, out output : i7) +// BASIC: sv.func @test11(in %input : i7 {hw.arg = "arg"}, out output : i7 {hw.res = "res"}) +// BASIC: sv.func private @test12(in %input : i7, out output : i7) +// BASIC: sv.func private @test13(in %input : i7 {hw.arg = "arg"}, out output : i7 {hw.res = "res"}) +// BASIC: sv.func private @test14(in %input : i7, out output : i7) +// BASIC: sv.func private @test15(in %input : i7 {hw.arg = "arg"}, out output : i7 {hw.res = "res"}) // DEBUG: hw.module @test0(in %input : i7 loc({{.+}}), out output : i7 loc({{.+}})) // DEBUG: hw.module @test1(in %input : i7 {hw.arg = "arg"} loc({{.+}}), out output : i7 {hw.res = "res"} loc({{.+}})) @@ -30,3 +46,11 @@ hw.module.extern @test7(in %input: i7 {hw.arg = "arg"} loc("arg"), out output: i // DEBUG: hw.module.extern @test5(in %input : i7 {hw.arg = "arg"} loc({{.+}}), out output : i7 {hw.res = "res"} loc({{.+}})) // DEBUG: hw.module.extern @test6(in %input : i7 loc("arg"), out output : i7 loc("res")) // DEBUG: hw.module.extern @test7(in %input : i7 {hw.arg = "arg"} loc("arg"), out output : i7 {hw.res = "res"} loc("res")) +// DEBUG: sv.func @test8(in %input : i7 loc({{.+}}), out output : i7 loc({{.+}})) +// DEBUG: sv.func @test9(in %input : i7 {hw.arg = "arg"} loc({{.+}}), out output : i7 {hw.res = "res"} loc({{.+}})) +// DEBUG: sv.func @test10(in %input : i7 loc("arg"), out output : i7 loc("res")) +// DEBUG: sv.func @test11(in %input : i7 {hw.arg = "arg"} loc("arg"), out output : i7 {hw.res = "res"} loc("res")) +// DEBUG: sv.func private @test12(in %input : i7 loc({{.+}}), out output : i7 loc({{.+}})) +// DEBUG: sv.func private @test13(in %input : i7 {hw.arg = "arg"} loc({{.+}}), out output : i7 {hw.res = "res"} loc({{.+}})) +// DEBUG: sv.func private @test14(in %input : i7 loc("arg"), out output : i7 loc("res")) +// DEBUG: sv.func private @test15(in %input : i7 {hw.arg = "arg"} loc("arg"), out output : i7 {hw.res = "res"} loc("res")) diff --git a/test/Dialect/SV/basic.mlir b/test/Dialect/SV/basic.mlir index bf8bbda47a3d..3b61f6a58dd6 100644 --- a/test/Dialect/SV/basic.mlir +++ b/test/Dialect/SV/basic.mlir @@ -389,3 +389,22 @@ hw.module @XMRRefOp() { // CHECK: %1 = sv.xmr.ref @ref2 ".x.y.z[42]" : !hw.inout %1 = sv.xmr.ref @ref2 ".x.y.z[42]" : !hw.inout } + +// Functions. +// CHECK-LABEL: sv.func private @function_declare(in %in_0 : i2, in %in_1 : i2, out out_0 : i1, in %in_2 : !hw.array<2xi2>) +sv.func private @function_declare(in %in_0 : i2, in %in_1 : i2, out out_0 : i1, in %in_2 : !hw.array<2xi2>) + +// CHECK-LABEL: sv.func private @function_define(in %in_0 : i2, in %in_1 : i2, out out_0 : i1, in %in_2 : !hw.array<2xi2>) +sv.func private @function_define(in %in_0 : i2, in %in_1 : i2, out out_0 : i1, in %in_2 : !hw.array<2xi2>) attributes {test = "foo"} { + %0 = comb.icmp eq %in_0, %in_1: i2 + // CHECK: sv.return %{{.+}} : i1 + sv.return %0 : i1 +} + +// CHECK-LABEL: sv.func @recurse(in %n : i32, out out : i32) { +// CHECK: %0 = sv.func.call.procedural @recurse(%n) : (i32) -> i32 +// CHECK-NEXT: sv.return %0 +sv.func @recurse(in %n : i32, out out : i32) { + %v = sv.func.call.procedural @recurse(%n) : (i32) -> i32 + sv.return %v : i32 +} diff --git a/test/Dialect/SV/errors.mlir b/test/Dialect/SV/errors.mlir index 534b6aeeccff..954923ac9974 100644 --- a/test/Dialect/SV/errors.mlir +++ b/test/Dialect/SV/errors.mlir @@ -238,3 +238,45 @@ hw.module @NoMessage(in %clock: i1, in %value : i4) { "sv.assert"(%clock, %value) { defer = 0 : i32 } : (i1, i4) -> () } } + +// ----- + +sv.func private @function() { + %0 = hw.constant true + // expected-error @below {{'sv.return' op must have same number of operands as region results}} + sv.return %0 : i1 +} + +// ----- + +sv.func private @function(out out: i2) { + %0 = hw.constant true + // expected-error @below {{'sv.return' op output types must match function. In operand 0, expected 'i2', but got 'i1'}} + sv.return %0 : i1 +} + +// ----- + +hw.module private @module(out out: i2) { + %0 = hw.constant true + // expected-error @below {{'sv.return' op expects parent op 'sv.func'}} + sv.return %0 : i1 +} + +// ----- + +// expected-note @below {{doesn't satisfy the constraint}} +sv.func private @func(out out: i1) +hw.module private @call(){ + // expected-error @below {{function called in a non-procedural region must return a single result}} + %0 = sv.func.call @func() : () -> (i1) +} + +// ----- + +sv.func private @func() { + sv.return +} + +// expected-error @below {{imported function must be a declaration but 'func' is defined}} +sv.func.dpi.import @func