From 7556e9798738944d026053f5983a6b53628f92ce Mon Sep 17 00:00:00 2001 From: Prithayan Barua Date: Wed, 20 Sep 2023 07:26:33 -0700 Subject: [PATCH] [ExportVerilog] Add verilog debug locations to output MLIR (#6092) Use the PrettyPrinter CallbackToken API, to record print events and store the verilog output locations for each operation. Record the print begin and end location of each op. This has a side-effect of updating the IR, location attribute of the op is updated to include the verilog location range. `FusedLoc` is used to record the location ranges and each op can correspond to multiple verilog locations. This feature is disabled by default under an emission flag `emitVerilogLocations`. --- include/circt/Support/LoweringOptions.h | 3 + include/circt/Support/PrettyPrinterHelpers.h | 24 ++ .../ExportVerilog/ExportVerilog.cpp | 172 ++++++++++++-- .../ExportVerilog/ExportVerilogInternals.h | 103 +++++++- lib/Support/LoweringOptions.cpp | 4 + .../ExportVerilog/verilog-locations.mlir | 223 ++++++++++++++++++ 6 files changed, 507 insertions(+), 22 deletions(-) create mode 100644 test/Conversion/ExportVerilog/verilog-locations.mlir diff --git a/include/circt/Support/LoweringOptions.h b/include/circt/Support/LoweringOptions.h index 3ab65be94490..59ce7aadc966 100644 --- a/include/circt/Support/LoweringOptions.h +++ b/include/circt/Support/LoweringOptions.h @@ -163,6 +163,9 @@ struct LoweringOptions { /// This is used to avoid stricter lint warnings which, e.g., treat "REG" as a /// Verilog keyword. bool caseInsensitiveKeywords = false; + + /// If true, then update the the mlir to include output verilog locations. + bool emitVerilogLocations = false; }; } // namespace circt diff --git a/include/circt/Support/PrettyPrinterHelpers.h b/include/circt/Support/PrettyPrinterHelpers.h index 59c7433a2b84..c1c1b90b0b8f 100644 --- a/include/circt/Support/PrettyPrinterHelpers.h +++ b/include/circt/Support/PrettyPrinterHelpers.h @@ -240,6 +240,8 @@ struct PPSaveString { template class TokenStream : public TokenBuilder { using Base = TokenBuilder; + +protected: TokenStringSaver &saver; public: @@ -369,6 +371,28 @@ class TokenStream : public TokenBuilder { } }; +/// Wrap the TokenStream with a helper for CallbackTokens, to record the print +/// events on the stream. +template +class TokenStreamWithCallback : public TokenStream { + using Base = TokenStream; + PrintEventAndStorageListener &saver; + + const bool enableCallback; + +public: + TokenStreamWithCallback( + PPTy &pp, PrintEventAndStorageListener &saver, + bool enableCallback) + : TokenStream(pp, saver), saver(saver), + enableCallback(enableCallback) {} + /// Add a Callback token. + void addCallback(DataType d) { + if (enableCallback) + Base::addToken(saver.getToken(d)); + } +}; } // end namespace pretty } // end namespace circt diff --git a/lib/Conversion/ExportVerilog/ExportVerilog.cpp b/lib/Conversion/ExportVerilog/ExportVerilog.cpp index 472d122a5675..92853ece4194 100644 --- a/lib/Conversion/ExportVerilog/ExportVerilog.cpp +++ b/lib/Conversion/ExportVerilog/ExportVerilog.cpp @@ -33,6 +33,7 @@ #include "circt/Support/LLVM.h" #include "circt/Support/LoweringOptions.h" #include "circt/Support/Path.h" +#include "circt/Support/PrettyPrinter.h" #include "circt/Support/PrettyPrinterHelpers.h" #include "circt/Support/Version.h" #include "mlir/IR/BuiltinOps.h" @@ -47,6 +48,7 @@ #include "llvm/ADT/StringSet.h" #include "llvm/ADT/TypeSwitch.h" #include "llvm/Support/FileSystem.h" +#include "llvm/Support/FormattedStream.h" #include "llvm/Support/Path.h" #include "llvm/Support/SaveAndRestore.h" #include "llvm/Support/ToolOutputFile.h" @@ -1010,10 +1012,12 @@ class VerilogEmitterState { const LoweringOptions &options, const HWSymbolCache &symbolCache, const GlobalNameTable &globalNames, - raw_ostream &os) + llvm::formatted_raw_ostream &os, + StringAttr fileName, OpLocMap &verilogLocMap) : designOp(designOp), shared(shared), options(options), symbolCache(symbolCache), globalNames(globalNames), os(os), - pp(os, options.emittedLineLength) { + verilogLocMap(verilogLocMap), pp(os, options.emittedLineLength), + fileName(fileName) { pp.setListener(&saver); } /// This is the root mlir::ModuleOp that holds the whole design being emitted. @@ -1031,8 +1035,10 @@ class VerilogEmitterState { /// the IR name. const GlobalNameTable &globalNames; - /// The stream to emit to. - raw_ostream &os; + /// The stream to emit to. Use a formatted_raw_ostream, to easily get the + /// current location(line,column) on the stream. This is required to record + /// the verilog output location information corresponding to any op. + llvm::formatted_raw_ostream &os; bool encounteredError = false; unsigned currentIndent = 0; @@ -1046,13 +1052,30 @@ class VerilogEmitterState { /// this. bool pendingNewline = false; + /// Used to record the verilog output file location of an op. + OpLocMap &verilogLocMap; /// String storage backing Tokens built from temporary strings. /// PrettyPrinter will clear this as appropriate. - TokenStringSaver saver; + PrintEventAndStorageListener> saver = + PrintEventAndStorageListener>( + verilogLocMap); /// Pretty printer. PrettyPrinter pp; + /// Name of the output file, used for debug information. + StringAttr fileName; + + /// Update the location attribute of the ops with the verilog locations + /// recorded in `verilogLocMap` and clear the map. `lineOffset` is added to + /// all the line numbers, this is required when the modules are exported in + /// parallel. + void addVerilogLocToOps(unsigned int lineOffset, StringAttr fileName) { + verilogLocMap.updateIRWithLoc(lineOffset, fileName, + shared.designOp->getContext()); + verilogLocMap.clear(); + } + private: VerilogEmitterState(const VerilogEmitterState &) = delete; void operator=(const VerilogEmitterState &) = delete; @@ -1065,16 +1088,21 @@ class VerilogEmitterState { namespace { +/// The data that is unique to each callback. The operation and a flag to +/// indicate if the callback is for begin or end of the operation print +/// location. +using CallbackDataTy = std::pair; class EmitterBase { public: // All of the mutable state we are maintaining. VerilogEmitterState &state; /// Stream helper (pp, saver). - TokenStream<> ps; + TokenStreamWithCallback ps; explicit EmitterBase(VerilogEmitterState &state) - : state(state), ps(state.pp, state.saver) {} + : state(state), + ps(state.pp, state.saver, state.options.emitVerilogLocations) {} InFlightDiagnostic emitError(Operation *op, const Twine &message) { state.encounteredError = true; @@ -1986,7 +2014,8 @@ class ExprEmitter : public EmitterBase, SmallPtrSetImpl &emittedExprs, BufferingPP::BufferVec &tokens) : EmitterBase(emitter.state), emitter(emitter), - emittedExprs(emittedExprs), buffer(tokens), ps(buffer, state.saver) { + emittedExprs(emittedExprs), buffer(tokens), + ps(buffer, state.saver, state.options.emitVerilogLocations) { assert(state.pp.getListener() == &state.saver); } @@ -2268,7 +2297,7 @@ class ExprEmitter : public EmitterBase, BufferingPP buffer; /// Stream to emit expressions into, will add to buffer. - TokenStream ps; + TokenStreamWithCallback ps; }; } // end anonymous namespace @@ -2407,6 +2436,12 @@ SubExprInfo ExprEmitter::emitSubExpr(Value exp, } unsigned subExprStartIndex = buffer.tokens.size(); + if (op) + ps.addCallback({op, true}); + auto done = llvm::make_scope_exit([&]() { + if (op) + ps.addCallback({op, false}); + }); // Inform the visit method about the preferred sign we want from the result. // It may choose to ignore this, but some emitters can change behavior based @@ -3199,7 +3234,8 @@ class PropertyEmitter : public EmitterBase, SmallPtrSetImpl &emittedOps, BufferingPP::BufferVec &tokens) : EmitterBase(emitter.state), emitter(emitter), emittedOps(emittedOps), - buffer(tokens), ps(buffer, state.saver) { + buffer(tokens), + ps(buffer, state.saver, state.options.emitVerilogLocations) { assert(state.pp.getListener() == &state.saver); } @@ -3247,7 +3283,7 @@ class PropertyEmitter : public EmitterBase, BufferingPP buffer; /// Stream to emit expressions into, will add to buffer. - TokenStream ps; + TokenStreamWithCallback ps; }; } // end anonymous namespace @@ -3720,10 +3756,12 @@ StmtEmitter::emitAssignLike(Op op, PPExtString syntax, ops.insert(op); startStatement(); + ps.addCallback({op, true}); emitAssignLike([&]() { emitExpression(op.getDest(), ops); }, [&]() { emitExpression(op.getSrc(), ops); }, syntax, PPExtString(";"), wordBeforeLHS); + ps.addCallback({op, false}); emitLocationInfoAndNewLine(ops); return success(); } @@ -3775,11 +3813,13 @@ LogicalResult StmtEmitter::visitSV(ReleaseOp op) { startStatement(); SmallPtrSet ops; ops.insert(op); + ps.addCallback({op, true}); ps.scopedBox(PP::ibox2, [&]() { ps << "release" << PP::space; emitExpression(op.getDest(), ops); ps << ";"; }); + ps.addCallback({op, false}); emitLocationInfoAndNewLine(ops); return success(); } @@ -3791,6 +3831,7 @@ LogicalResult StmtEmitter::visitSV(AliasOp op) { startStatement(); SmallPtrSet ops; ops.insert(op); + ps.addCallback({op, true}); ps.scopedBox(PP::ibox2, [&]() { ps << "alias" << PP::space; ps.scopedBox(PP::cbox0, [&]() { // If any breaks, all break. @@ -3800,6 +3841,7 @@ LogicalResult StmtEmitter::visitSV(AliasOp op) { ps << ";"; }); }); + ps.addCallback({op, false}); emitLocationInfoAndNewLine(ops); return success(); } @@ -3814,6 +3856,7 @@ LogicalResult StmtEmitter::visitSV(InterfaceInstanceOp op) { startStatement(); StringRef prefix = ""; + ps.addCallback({op, true}); if (doNotPrint) { prefix = "// "; ps << "// This interface is elsewhere emitted as a bind statement." @@ -3834,6 +3877,7 @@ LogicalResult StmtEmitter::visitSV(InterfaceInstanceOp op) { << PP::nbsp /* don't break, may be comment line */ << PPExtString(op.getName()) << "();"; + ps.addCallback({op, false}); emitLocationInfoAndNewLine(ops); return success(); @@ -3862,6 +3906,7 @@ LogicalResult StmtEmitter::visitStmt(OutputOp op) { ops.insert(op); startStatement(); + ps.addCallback({op, true}); bool isZeroBit = isZeroBitType(port.type); ps.scopedBox(isZeroBit ? PP::neverbox : PP::ibox2, [&]() { if (isZeroBit) @@ -3881,6 +3926,7 @@ LogicalResult StmtEmitter::visitStmt(OutputOp op) { ps << ";"; }); }); + ps.addCallback({op, false}); emitLocationInfoAndNewLine(ops); ++operandIndex; @@ -3937,6 +3983,7 @@ LogicalResult StmtEmitter::visitSV(FWriteOp op) { SmallPtrSet ops; ops.insert(op); + ps.addCallback({op, true}); ps << "$fwrite("; ps.scopedBox(PP::ibox0, [&]() { emitExpression(op.getFd(), ops); @@ -3956,6 +4003,7 @@ LogicalResult StmtEmitter::visitSV(FWriteOp op) { } ps << ");"; }); + ps.addCallback({op, false}); emitLocationInfoAndNewLine(ops); return success(); } @@ -4011,6 +4059,7 @@ StmtEmitter::emitSimulationControlTask(Operation *op, PPExtString taskName, startStatement(); SmallPtrSet ops; ops.insert(op); + ps.addCallback({op, true}); ps << taskName; if (verbosity && *verbosity != 1) { ps << "("; @@ -4018,6 +4067,7 @@ StmtEmitter::emitSimulationControlTask(Operation *op, PPExtString taskName, ps << ")"; } ps << ";"; + ps.addCallback({op, false}); emitLocationInfoAndNewLine(ops); return success(); } @@ -4047,6 +4097,7 @@ StmtEmitter::emitSeverityMessageTask(Operation *op, PPExtString taskName, startStatement(); SmallPtrSet ops; ops.insert(op); + ps.addCallback({op, true}); ps << taskName; // In case we have a message to print, or the operation has an optional @@ -4077,6 +4128,7 @@ StmtEmitter::emitSeverityMessageTask(Operation *op, PPExtString taskName, } ps << ";"; + ps.addCallback({op, false}); emitLocationInfoAndNewLine(ops); return success(); } @@ -4105,6 +4157,7 @@ LogicalResult StmtEmitter::visitSV(ReadMemOp op) { SmallPtrSet ops({op}); startStatement(); + ps.addCallback({op, true}); ps << "$readmem"; switch (op.getBaseAttr().getValue()) { case MemBaseTypeAttr::MemBaseBin: @@ -4122,6 +4175,7 @@ LogicalResult StmtEmitter::visitSV(ReadMemOp op) { }); ps << ");"; + ps.addCallback({op, false}); emitLocationInfoAndNewLine(ops); return success(); } @@ -4130,6 +4184,7 @@ LogicalResult StmtEmitter::visitSV(GenerateOp op) { emitSVAttributes(op); // TODO: location info? startStatement(); + ps.addCallback({op, true}); ps << "generate" << PP::newline; ps << "begin: " << PPExtString(getSymOpName(op)); setPendingNewline(); @@ -4137,6 +4192,7 @@ LogicalResult StmtEmitter::visitSV(GenerateOp op) { startStatement(); ps << "end: " << PPExtString(getSymOpName(op)) << PP::newline; ps << "endgenerate"; + ps.addCallback({op, false}); setPendingNewline(); return success(); } @@ -4145,6 +4201,7 @@ LogicalResult StmtEmitter::visitSV(GenerateCaseOp op) { emitSVAttributes(op); // TODO: location info? startStatement(); + ps.addCallback({op, true}); ps << "case ("; ps.invokeWithStringOS([&](auto &os) { emitter.printParamValue( @@ -4196,6 +4253,7 @@ LogicalResult StmtEmitter::visitSV(GenerateCaseOp op) { startStatement(); ps << "endcase"; + ps.addCallback({op, false}); setPendingNewline(); return success(); } @@ -4203,6 +4261,7 @@ LogicalResult StmtEmitter::visitSV(GenerateCaseOp op) { LogicalResult StmtEmitter::visitSV(ForOp op) { emitSVAttributes(op); llvm::SmallPtrSet ops; + ps.addCallback({op, true}); startStatement(); auto inductionVarName = op->getAttrOfType("hw.verilogName"); ps << "for ("; @@ -4240,6 +4299,7 @@ LogicalResult StmtEmitter::visitSV(ForOp op) { emitStatementBlock(op.getBody().getBlocks().front()); startStatement(); ps << "end"; + ps.addCallback({op, false}); emitLocationInfoAndNewLine(ops); return success(); } @@ -4277,6 +4337,7 @@ LogicalResult StmtEmitter::emitImmediateAssertion(Op op, PPExtString opName) { startStatement(); SmallPtrSet ops; ops.insert(op); + ps.addCallback({op, true}); ps.scopedBox(PP::ibox2, [&]() { emitAssertionLabel(op); ps.scopedBox(PP::cbox0, [&]() { @@ -4300,6 +4361,7 @@ LogicalResult StmtEmitter::emitImmediateAssertion(Op op, PPExtString opName) { ps << ";"; }); }); + ps.addCallback({op, false}); emitLocationInfoAndNewLine(ops); return success(); } @@ -4324,6 +4386,7 @@ LogicalResult StmtEmitter::emitConcurrentAssertion(Op op, PPExtString opName) { startStatement(); SmallPtrSet ops; ops.insert(op); + ps.addCallback({op, true}); ps.scopedBox(PP::ibox2, [&]() { emitAssertionLabel(op); ps.scopedBox(PP::cbox0, [&]() { @@ -4341,6 +4404,7 @@ LogicalResult StmtEmitter::emitConcurrentAssertion(Op op, PPExtString opName) { ps << ";"; }); }); + ps.addCallback({op, false}); emitLocationInfoAndNewLine(ops); return success(); } @@ -4379,6 +4443,7 @@ LogicalResult StmtEmitter::emitVerifAssertLike(Operation *op, Value property, startStatement(); SmallPtrSet ops; ops.insert(op); + ps.addCallback({op, true}); ps.scopedBox(PP::ibox2, [&]() { emitAssertionLabel(op); ps.scopedBox(PP::cbox0, [&]() { @@ -4392,6 +4457,7 @@ LogicalResult StmtEmitter::emitVerifAssertLike(Operation *op, Value property, }); }); }); + ps.addCallback({op, false}); emitLocationInfoAndNewLine(ops); return success(); } @@ -4487,6 +4553,7 @@ LogicalResult StmtEmitter::visitSV(IfOp op) { emitSVAttributes(op); startStatement(); + ps.addCallback({op, true}); ps << "if (" << ifcondBox; // In the loop, emit an if statement assuming the keyword introducing @@ -4521,6 +4588,7 @@ LogicalResult StmtEmitter::visitSV(IfOp op) { ifOp = nestedElseIfOp; ps << "else if (" << ifcondBox; } + ps.addCallback({op, false}); return success(); } @@ -4535,6 +4603,7 @@ LogicalResult StmtEmitter::visitSV(AlwaysOp op) { ps << PPExtString(stringifyEventControl(cond.event)) << PP::nbsp; ps.scopedBox(PP::cbox0, [&]() { emitExpression(cond.value, ops); }); }; + ps.addCallback({op, true}); switch (op.getNumConditions()) { case 0: @@ -4576,6 +4645,7 @@ LogicalResult StmtEmitter::visitSV(AlwaysOp op) { } emitBlockAsStatement(op.getBodyBlock(), ops, comment); + ps.addCallback({op, false}); return success(); } @@ -4585,12 +4655,14 @@ LogicalResult StmtEmitter::visitSV(AlwaysCombOp op) { ops.insert(op); startStatement(); + ps.addCallback({op, true}); StringRef opString = "always_comb"; if (state.options.noAlwaysComb) opString = "always @(*)"; ps << PPExtString(opString); emitBlockAsStatement(op.getBodyBlock(), ops, opString); + ps.addCallback({op, false}); return success(); } @@ -4601,6 +4673,7 @@ LogicalResult StmtEmitter::visitSV(AlwaysFFOp op) { ops.insert(op); startStatement(); + ps.addCallback({op, true}); ps << "always_ff @("; ps.scopedBox(PP::cbox0, [&]() { ps << PPExtString(stringifyEventControl(op.getClockEdge())) << PP::nbsp; @@ -4651,6 +4724,7 @@ LogicalResult StmtEmitter::visitSV(AlwaysFFOp op) { ps << " // " << comment; setPendingNewline(); } + ps.addCallback({op, false}); return success(); } @@ -4659,8 +4733,10 @@ LogicalResult StmtEmitter::visitSV(InitialOp op) { SmallPtrSet ops; ops.insert(op); startStatement(); + ps.addCallback({op, true}); ps << "initial"; emitBlockAsStatement(op.getBodyBlock(), ops, "initial"); + ps.addCallback({op, false}); return success(); } @@ -4669,6 +4745,7 @@ LogicalResult StmtEmitter::visitSV(CaseOp op) { SmallPtrSet ops, emptyOps; ops.insert(op); startStatement(); + ps.addCallback({op, true}); if (op.getValidationQualifier() != ValidationQualifierTypeEnum::ValidationQualifierPlain) ps << PPExtString(circt::sv::stringifyValidationQualifierTypeEnum( @@ -4722,6 +4799,7 @@ LogicalResult StmtEmitter::visitSV(CaseOp op) { startStatement(); ps << "endcase"; + ps.addCallback({op, false}); emitLocationInfoAndNewLine(ops); return success(); } @@ -4735,6 +4813,7 @@ LogicalResult StmtEmitter::visitStmt(InstanceOp op) { if (!doNotPrint) emitSVAttributes(op); startStatement(); + ps.addCallback({op, true}); if (doNotPrint) { ps << PP::ibox2 << "/* This instance is elsewhere emitted as a bind statement." @@ -4893,6 +4972,7 @@ LogicalResult StmtEmitter::visitStmt(InstanceOp op) { startStatement(); } ps << ");"; + ps.addCallback({op, false}); emitLocationInfoAndNewLine(ops); if (doNotPrint) { ps << PP::end; @@ -4921,12 +5001,14 @@ LogicalResult StmtEmitter::visitSV(InterfaceOp op) { emitSVAttributes(op); // TODO: source info! startStatement(); + ps.addCallback({op, true}); ps << "interface " << PPExtString(getSymOpName(op)) << ";"; setPendingNewline(); // FIXME: Don't emit the body of this as general statements, they aren't! emitStatementBlock(*op.getBodyBlock()); startStatement(); ps << "endinterface" << PP::newline; + ps.addCallback({op, false}); setPendingNewline(); return success(); } @@ -4935,6 +5017,7 @@ LogicalResult StmtEmitter::visitSV(InterfaceSignalOp op) { // Emit SV attributes. emitSVAttributes(op); startStatement(); + ps.addCallback({op, true}); if (isZeroBitType(op.getType())) ps << PP::neverbox << "// "; ps.invokeWithStringOS([&](auto &os) { @@ -4947,12 +5030,14 @@ LogicalResult StmtEmitter::visitSV(InterfaceSignalOp op) { ps << ";"; if (isZeroBitType(op.getType())) ps << PP::end; // Close never-break group. + ps.addCallback({op, false}); setPendingNewline(); return success(); } LogicalResult StmtEmitter::visitSV(InterfaceModportOp op) { startStatement(); + ps.addCallback({op, true}); ps << "modport " << PPExtString(getSymOpName(op)) << "("; // TODO: revisit, better breaks/grouping. @@ -4964,12 +5049,14 @@ LogicalResult StmtEmitter::visitSV(InterfaceModportOp op) { }); ps << ");"; + ps.addCallback({op, false}); setPendingNewline(); return success(); } LogicalResult StmtEmitter::visitSV(AssignInterfaceSignalOp op) { startStatement(); + ps.addCallback({op, true}); SmallPtrSet emitted; // TODO: emit like emitAssignLike does, maybe refactor. ps << "assign "; @@ -4977,6 +5064,7 @@ LogicalResult StmtEmitter::visitSV(AssignInterfaceSignalOp op) { ps << "." << PPExtString(op.getSignalName()) << " = "; emitExpression(op.getRhs(), emitted); ps << ";"; + ps.addCallback({op, false}); setPendingNewline(); return success(); } @@ -4985,6 +5073,7 @@ LogicalResult StmtEmitter::visitSV(MacroDefOp op) { auto decl = op.getReferencedMacro(&state.symbolCache); // TODO: source info! startStatement(); + ps.addCallback({op, true}); ps << "`define " << PPExtString(getSymOpName(decl)); if (decl.getArgs()) { ps << "("; @@ -4998,6 +5087,7 @@ LogicalResult StmtEmitter::visitSV(MacroDefOp op) { emitTextWithSubstitutions(ps, op.getFormatString(), op, {}, op.getSymbols()); } + ps.addCallback({op, false}); setPendingNewline(); return success(); } @@ -5153,6 +5243,7 @@ LogicalResult StmtEmitter::emitDeclaration(Operation *op) { SmallPtrSet opsForLocation; opsForLocation.insert(op); startStatement(); + ps.addCallback({op, true}); // Emit the leading word, like 'wire', 'reg' or 'logic'. auto type = value.getType(); @@ -5263,6 +5354,7 @@ LogicalResult StmtEmitter::emitDeclaration(Operation *op) { } ps << ";"; }); + ps.addCallback({op, false}); emitLocationInfoAndNewLine(opsForLocation); return success(); } @@ -5328,8 +5420,10 @@ void ModuleEmitter::emitSVAttributes(Operation *op) { void ModuleEmitter::emitHWExternModule(HWModuleExternOp module) { auto verilogName = module.getVerilogModuleNameAttr(); startStatement(); + ps.addCallback({module, true}); ps << "// external module " << PPExtString(verilogName.getValue()) << PP::newline; + ps.addCallback({module, false}); setPendingNewline(); } @@ -5360,6 +5454,7 @@ void ModuleEmitter::emitBind(BindOp op) { auto childVerilogName = getVerilogModuleNameAttr(childMod); startStatement(); + ps.addCallback({op, true}); ps << "bind " << PPExtString(parentVerilogName.getValue()) << PP::nbsp << PPExtString(childVerilogName.getValue()) << PP::nbsp << PPExtString(getSymOpName(inst)) << " ("; @@ -5446,6 +5541,7 @@ void ModuleEmitter::emitBind(BindOp op) { if (!isFirst) ps << PP::newline; ps << ");"; + ps.addCallback({op, false}); setPendingNewline(); } @@ -5458,9 +5554,11 @@ void ModuleEmitter::emitBindInterface(BindInterfaceOp op) { auto *interface = op->getParentOfType().lookupSymbol( instance.getInterfaceType().getInterface()); startStatement(); + ps.addCallback({op, true}); ps << "bind " << PPExtString(instantiator) << PP::nbsp << PPExtString(cast(*interface).getSymName()) << PP::nbsp << PPExtString(getSymOpName(instance)) << " (.*);" << PP::newline; + ps.addCallback({op, false}); setPendingNewline(); } @@ -5725,6 +5823,7 @@ void ModuleEmitter::emitHWModule(HWModuleOp module) { emitComment(module.getCommentAttr()); emitSVAttributes(module); startStatement(); + ps.addCallback({module, true}); ps << "module " << PPExtString(getVerilogModuleName(module)); // If we have any parameters, print them on their own line. @@ -5737,7 +5836,9 @@ void ModuleEmitter::emitHWModule(HWModuleOp module) { // Emit the body of the module. StmtEmitter(*this, state.options).emitStatementBlock(*module.getBodyBlock()); startStatement(); - ps << "endmodule" << PP::newline; + ps << "endmodule"; + ps.addCallback({module, false}); + ps << PP::newline; setPendingNewline(); currentModuleOp = nullptr; @@ -6039,8 +6140,9 @@ static void emitOperation(VerilogEmitterState &state, Operation *op) { /// Actually emit the collected list of operations and strings to the /// specified file. -void SharedEmitterState::emitOps(EmissionList &thingsToEmit, raw_ostream &os, - bool parallelize) { +void SharedEmitterState::emitOps(EmissionList &thingsToEmit, + llvm::formatted_raw_ostream &os, + StringAttr fileName, bool parallelize) { MLIRContext *context = designOp->getContext(); // Disable parallelization overhead if MLIR threading is disabled. @@ -6050,13 +6152,23 @@ void SharedEmitterState::emitOps(EmissionList &thingsToEmit, raw_ostream &os, // If we aren't parallelizing output, directly output each operation to the // specified stream. if (!parallelize) { + // All the modules share the same map to store the verilog output location + // on the stream. + OpLocMap verilogLocMap(os); VerilogEmitterState state(designOp, *this, options, symbolCache, - globalNames, os); + globalNames, os, fileName, verilogLocMap); + size_t lineOffset = 0; for (auto &entry : thingsToEmit) { - if (auto *op = entry.getOperation()) + entry.verilogLocs.setStream(os); + if (auto *op = entry.getOperation()) { emitOperation(state, op); - else + // Since the modules are exported sequentially, update all the ops with + // the verilog location. This also clears the map, so that the map only + // contains the current iteration's ops. + state.addVerilogLocToOps(lineOffset, fileName); + } else os << entry.getStringData(); + ++lineOffset; } if (state.encounteredError) @@ -6079,8 +6191,12 @@ void SharedEmitterState::emitOps(EmissionList &thingsToEmit, raw_ostream &os, SmallString<256> buffer; llvm::raw_svector_ostream tmpStream(buffer); + llvm::formatted_raw_ostream rs(tmpStream); + // Each `thingToEmit` (op) uses a unique map to store verilog locations. + stringOrOp.verilogLocs.setStream(rs); VerilogEmitterState state(designOp, *this, options, symbolCache, - globalNames, tmpStream); + globalNames, rs, fileName, + stringOrOp.verilogLocs); emitOperation(state, op); stringOrOp.setString(buffer); }); @@ -6091,14 +6207,21 @@ void SharedEmitterState::emitOps(EmissionList &thingsToEmit, raw_ostream &os, // the output stream. auto *op = entry.getOperation(); if (!op) { + auto lineOffset = os.getLine() + 1; os << entry.getStringData(); + // Ensure the line numbers are offset properly in the map. Each `entry` + // was exported in parallel onto independent string streams, hence the + // line numbers need to be updated with the offset in the current stream. + entry.verilogLocs.updateIRWithLoc(lineOffset, fileName, context); continue; } + entry.verilogLocs.setStream(os); // If this wasn't emitted to a string (e.g. it is a bind) do so now. VerilogEmitterState state(designOp, *this, options, symbolCache, - globalNames, os); + globalNames, os, fileName, entry.verilogLocs); emitOperation(state, op); + state.addVerilogLocToOps(0, fileName); } } @@ -6140,8 +6263,12 @@ static LogicalResult exportVerilogImpl(ModuleOp module, llvm::raw_ostream &os) { list.emplace_back(contents); } + llvm::formatted_raw_ostream rs(os); // Finally, emit all the ops we collected. - emitter.emitOps(list, os, /*parallelize=*/true); + // output file name is not known, it can be specified as command line + // argument. + emitter.emitOps(list, rs, StringAttr::get(module.getContext(), ""), + /*parallelize=*/true); return failure(emitter.encounteredError); } @@ -6229,11 +6356,14 @@ static void createSplitOutputFile(StringAttr fileName, FileInfo &file, emitter.collectOpsForFile(file, list, emitter.options.emitReplicatedOpsToHeader); + llvm::formatted_raw_ostream rs(output->os()); // Emit the file, copying the global options into the individual module // state. Don't parallelize emission of the ops within this file - we // already parallelize per-file emission and we pay a string copy overhead // for parallelization. - emitter.emitOps(list, output->os(), /*parallelize=*/false); + emitter.emitOps(list, rs, + StringAttr::get(fileName.getContext(), output->getFilename()), + /*parallelize=*/false); output->keep(); } diff --git a/lib/Conversion/ExportVerilog/ExportVerilogInternals.h b/lib/Conversion/ExportVerilog/ExportVerilogInternals.h index c9101a73cbd8..70627fda5878 100644 --- a/lib/Conversion/ExportVerilog/ExportVerilogInternals.h +++ b/lib/Conversion/ExportVerilog/ExportVerilogInternals.h @@ -15,8 +15,10 @@ #include "circt/Dialect/HW/HWVisitors.h" #include "circt/Dialect/SV/SVOps.h" #include "circt/Dialect/SV/SVVisitors.h" +#include "mlir/IR/Location.h" #include "llvm/ADT/MapVector.h" #include "llvm/ADT/SmallPtrSet.h" +#include "llvm/Support/FormattedStream.h" #include namespace circt { @@ -176,6 +178,100 @@ struct FileInfo { bool isVerilog = true; }; +/// Track the output verilog line,column number information for every op. +class OpLocMap { + /// Record the output location from where the op begins to print. + void addBeginLoc(Operation *op) { + map[op].emplace_back(LocationRange(LineColPair(*fStream))); + } + /// Record the output location where the op ends to print. + void addEndLoc(Operation *op) { + assert(!map[op].empty()); + assert(map[op].back().begin.isValid()); + assert(!map[op].back().end.isValid()); + map[op].back().end = LineColPair(*fStream); + } + +public: + /// Data that is unique to each callback. The op and whether its a begin or + /// end location. + using DataType = std::pair; + + OpLocMap(llvm::formatted_raw_ostream &fStream) : fStream(&fStream) {} + OpLocMap() = default; + + /// Set the output stream. + void setStream(llvm::formatted_raw_ostream &f) { fStream = &f; } + /// Callback operator, invoked on the print events indicated by `data`. + void operator()(DataType data) { + assert(fStream); + auto beginPrint = data.second; + auto *op = data.first; + if (beginPrint) + addBeginLoc(op); + else + addEndLoc(op); + } + + /// Called after the verilog has been exported and the corresponding locations + /// are recorded in the map. + void updateIRWithLoc(unsigned lineOffset, StringAttr fileName, + MLIRContext *context) { + if (map.empty()) + return; + if (!verilogLineAttr) { + verilogLineAttr = StringAttr::get(context, "verilogLocations"); + metadataAttr = StringAttr::get(context, "Range"); + } + for (auto &[op, locations] : map) { + // An operation can have multiple verilog locations. + SmallVector verilogLocs; + for (auto &loc : locations) { + // Create a location range attribute. + SmallVector beginEndPair; + assert(loc.begin.isValid() && loc.end.isValid()); + beginEndPair.emplace_back(mlir::FileLineColLoc::get( + fileName, loc.begin.line + lineOffset, loc.begin.col)); + beginEndPair.emplace_back(mlir::FileLineColLoc::get( + fileName, loc.end.line + lineOffset, loc.end.col)); + // Add it to the verilog locations of the op. + verilogLocs.emplace_back( + mlir::FusedLoc::get(context, beginEndPair, metadataAttr)); + } + // Update the location attribute with a fused loc of the original location + // and verilog locations. + op->setLoc(mlir::FusedLoc::get( + context, {op->getLoc(), mlir::FusedLoc::get(context, verilogLocs, + verilogLineAttr)})); + } + } + void clear() { map.clear(); } + +private: + struct LineColPair { + unsigned line = ~0U; + unsigned col = ~0U; + LineColPair() = default; + /// Given an output stream, store the current offset. + LineColPair(llvm::formatted_raw_ostream &s) + : line(s.getLine()), col(s.getColumn()) {} + bool isValid() { return (line != -1U && col != -1U); } + }; + struct LocationRange { + LineColPair begin; + LineColPair end; + LocationRange(LineColPair begin) : begin(begin) {} + }; + using Locations = SmallVector; + /// Map to store the verilog locations for each op. + DenseMap map; + /// The corresponding output stream, which provides the current print location + /// on the stream. + llvm::formatted_raw_ostream *fStream; + /// Cache to store string attributes. + StringAttr verilogLineAttr, metadataAttr; +}; + /// This class wraps an operation or a fixed string that should be emitted. class StringOrOpToEmit { public: @@ -218,6 +314,10 @@ class StringOrOpToEmit { rhs.pointerData = (Operation *)nullptr; } + /// Verilog output location information for entry. This is + /// required since each entry can be emitted in parallel. + OpLocMap verilogLocs; + private: StringOrOpToEmit(const StringOrOpToEmit &) = delete; void operator=(const StringOrOpToEmit &) = delete; @@ -273,7 +373,8 @@ struct SharedEmitterState { void collectOpsForFile(const FileInfo &fileInfo, EmissionList &thingsToEmit, bool emitHeader = false); - void emitOps(EmissionList &thingsToEmit, raw_ostream &os, bool parallelize); + void emitOps(EmissionList &thingsToEmit, llvm::formatted_raw_ostream &os, + StringAttr fileName, bool parallelize); }; //===----------------------------------------------------------------------===// diff --git a/lib/Support/LoweringOptions.cpp b/lib/Support/LoweringOptions.cpp index 2015dd05b780..d6dfff5ee97f 100644 --- a/lib/Support/LoweringOptions.cpp +++ b/lib/Support/LoweringOptions.cpp @@ -119,6 +119,8 @@ void LoweringOptions::parse(StringRef text, ErrorHandlerT errorHandler) { omitVersionComment = true; } else if (option == "caseInsensitiveKeywords") { caseInsensitiveKeywords = true; + } else if (option == "emitVerilogLocations") { + emitVerilogLocations = true; } else { errorHandler(llvm::Twine("unknown style option \'") + option + "\'"); // We continue parsing options after a failure. @@ -176,6 +178,8 @@ std::string LoweringOptions::toString() const { options += "omitVersionComment,"; if (caseInsensitiveKeywords) options += "caseInsensitiveKeywords,"; + if (emitVerilogLocations) + options += "emitVerilogLocations,"; // Remove a trailing comma if present. if (!options.empty()) { diff --git a/test/Conversion/ExportVerilog/verilog-locations.mlir b/test/Conversion/ExportVerilog/verilog-locations.mlir new file mode 100644 index 000000000000..af7b8e87e102 --- /dev/null +++ b/test/Conversion/ExportVerilog/verilog-locations.mlir @@ -0,0 +1,223 @@ +// RUN: circt-opt %s -export-verilog -verify-diagnostics --mlir-print-debuginfo --split-input-file | FileCheck %s --strict-whitespace + +module attributes {circt.loweringOptions = "emitVerilogLocations"} { +hw.module @MultiUseExpr(%a: i4) -> (b0: i1) { + %0 = comb.parity %a : i4 + hw.output %0 : i1 +} +} +// Line 2: module MultiUseExpr( +// input [3:0] a, +// output b0 +// ); +// +// assign b0 = ^a; +// endmodule + +// CHECK: hw.module @MultiUseExpr +// CHECK: %[[v0:.+]] = comb.parity %a : i4 loc(#loc19) +// CHECK: hw.output %[[v0]] : i1 loc(#loc20) +// CHECK: } loc(#loc18) +// CHECK: #loc = loc("{{.+}}verilog-locations.mlir{{.*}}) +// CHECK: #loc1 = loc("{{.+}}verilog-locations.mlir{{.*}}) +// CHECK: #loc2 = loc("":2:0) +// CHECK: #loc3 = loc("":8:9) +// CHECK: #loc6 = loc("{{.+}}verilog-locations.mlir{{.*}}) +// CHECK: #loc7 = loc("":7:14) +// CHECK: #loc8 = loc("":7:16) +// CHECK: #loc9 = loc("{{.+}}verilog-locations.mlir{{.*}}) +// CHECK: #loc10 = loc("":7:2) +// CHECK: #loc11 = loc("":7:17) +// CHECK: #loc12 = loc(fused<"Range">[#loc2, #loc3]) +// CHECK: #loc13 = loc(fused<"Range">[#loc7, #loc8]) +// CHECK: #loc14 = loc(fused<"Range">[#loc10, #loc11]) +// CHECK: #loc15 = loc(fused<"verilogLocations">[#loc12]) +// CHECK: #loc16 = loc(fused<"verilogLocations">[#loc13]) +// CHECK: #loc17 = loc(fused<"verilogLocations">[#loc14]) +// CHECK: #loc18 = loc(fused[#loc1, #loc15]) +// CHECK: #loc19 = loc(fused[#loc6, #loc16]) +// CHECK: #loc20 = loc(fused[#loc9, #loc17]) + +// ----- + +module attributes {circt.loweringOptions = "locationInfoStyle=none,emitVerilogLocations"} { +hw.module @SimpleConstPrintReset(%clock: i1, %reset: i1, %in4: i4) -> () { + %w = sv.wire : !hw.inout + %q = sv.reg : !hw.inout + %c1_i4 = hw.constant 1 : i4 + sv.assign %w, %c1_i4 : i4 + sv.always posedge %clock, posedge %reset { + sv.if %reset { + sv.passign %q, %c1_i4 : i4 + } else { + sv.passign %q, %in4 : i4 + } + } + hw.output +} +} +// module SimpleConstPrintReset( +// input clock, +// reset, +// input [3:0] in4 +// ); +// +// wire [3:0] w = 4'h1; +// reg [3:0] q; +// always @(posedge clock or posedge reset) begin +// if (reset) +// q <= 4'h1; +// else +// q <= in4; +// end // always @(posedge, posedge) +// endmodule +// CHECK: hw.module @SimpleConstPrintReset +// CHECK: %w = sv.wire {hw.verilogName = "w"} : !hw.inout loc(#loc49) +// CHECK: %q = sv.reg {hw.verilogName = "q"} : !hw.inout loc(#loc50) +// CHECK: %c1_i4 = hw.constant 1 : i4 loc(#loc51) +// CHECK: sv.assign %w, %c1_i4 : i4 loc(#loc18) +// CHECK: sv.always posedge %clock, posedge %reset { +// CHECK: sv.if %reset { +// CHECK: sv.passign %q, %c1_i4 : i4 loc(#loc54) +// CHECK: } else { +// CHECK: sv.passign %q, %in4 : i4 loc(#loc55) +// CHECK: } loc(#loc53) +// CHECK: } loc(#loc52) +// CHECK: hw.output loc(#loc30) +// CHECK: } loc(#loc48) +// CHECK: } loc(#loc) +// CHECK: #loc = loc("{{.*}}verilog-locations.mlir{{.*}}) +// CHECK: #loc1 = loc("{{.*}}verilog-locations.mlir{{.*}}) +// CHECK: #loc2 = loc("":2:0) +// CHECK: #loc3 = loc("":16:9) +// CHECK: #loc7 = loc("{{.*}}verilog-locations.mlir{{.*}}) +// CHECK: #loc8 = loc("":8:2) +// CHECK: #loc9 = loc("":8:22) +// CHECK: #loc10 = loc("{{.*}}verilog-locations.mlir{{.*}}) +// CHECK: #loc11 = loc("":9:2) +// CHECK: #loc12 = loc("":9:15) +// CHECK: #loc13 = loc("{{.*}}verilog-locations.mlir{{.*}}) +// CHECK: #loc14 = loc("":8:17) +// CHECK: #loc15 = loc("":8:21) +// CHECK: #loc16 = loc("":12:11) +// CHECK: #loc17 = loc("":12:15) +// CHECK: #loc18 = loc("{{.*}}verilog-locations.mlir{{.*}}) +// CHECK: #loc19 = loc("{{.*}}verilog-locations.mlir{{.*}}) +// CHECK: #loc20 = loc("":10:2) +// CHECK: #loc21 = loc("":15:35) +// CHECK: #loc22 = loc("{{.*}}verilog-locations.mlir{{.*}}) +// CHECK: #loc23 = loc("":11:4) +// CHECK: #loc24 = loc("":14:15) +// CHECK: #loc25 = loc("{{.*}}verilog-locations.mlir{{.*}}) +// CHECK: #loc26 = loc("":12:6) +// CHECK: #loc27 = loc("":12:16) +// CHECK: #loc28 = loc("{{.*}}verilog-locations.mlir{{.*}}) +// CHECK: #loc29 = loc("":14:6) +// CHECK: #loc30 = loc("{{.*}}verilog-locations.mlir{{.*}}) +// CHECK: #loc31 = loc(fused<"Range">[#loc2, #loc3]) +// CHECK: #loc32 = loc(fused<"Range">[#loc8, #loc9]) +// CHECK: #loc33 = loc(fused<"Range">[#loc11, #loc12]) +// CHECK: #loc34 = loc(fused<"Range">[#loc14, #loc15]) +// CHECK: #loc35 = loc(fused<"Range">[#loc16, #loc17]) +// CHECK: #loc36 = loc(fused<"Range">[#loc20, #loc21]) +// CHECK: #loc37 = loc(fused<"Range">[#loc23, #loc24]) +// CHECK: #loc38 = loc(fused<"Range">[#loc26, #loc27]) +// CHECK: #loc39 = loc(fused<"Range">[#loc29, #loc24]) +// CHECK: #loc40 = loc(fused<"verilogLocations">[#loc31]) +// CHECK: #loc41 = loc(fused<"verilogLocations">[#loc32]) +// CHECK: #loc42 = loc(fused<"verilogLocations">[#loc33]) +// CHECK: #loc43 = loc(fused<"verilogLocations">[#loc34, #loc35]) +// CHECK: #loc44 = loc(fused<"verilogLocations">[#loc36]) +// CHECK: #loc45 = loc(fused<"verilogLocations">[#loc37]) +// CHECK: #loc46 = loc(fused<"verilogLocations">[#loc38]) +// CHECK: #loc47 = loc(fused<"verilogLocations">[#loc39]) +// CHECK: #loc48 = loc(fused[#loc1, #loc40]) +// CHECK: #loc49 = loc(fused[#loc7, #loc41]) +// CHECK: #loc50 = loc(fused[#loc10, #loc42]) +// CHECK: #loc51 = loc(fused[#loc13, #loc43]) +// CHECK: #loc52 = loc(fused[#loc19, #loc44]) +// CHECK: #loc53 = loc(fused[#loc22, #loc45]) +// CHECK: #loc54 = loc(fused[#loc25, #loc46]) +// CHECK: #loc55 = loc(fused[#loc28, #loc47]) + +// ----- + +module attributes {circt.loweringOptions = "emitVerilogLocations"} { +hw.module @InlineDeclAssignment(%a: i1) { + %b = sv.wire : !hw.inout + sv.assign %b, %a : i1 + + %0 = comb.add %a, %a : i1 + %c = sv.wire : !hw.inout + sv.assign %c, %0 : i1 +} +} + +// module InlineDeclAssignment( +// input a +// ); +// +// wire b = a; +// wire c = a + a; +// endmodule +// +// CHECK: hw.module @InlineDeclAssignment +// CHECK: %b = sv.wire {hw.verilogName = "b"} : !hw.inout loc(#loc25) +// CHECK: sv.assign %b, %a : i1 loc(#loc8) +// CHECK: %[[v0:.+]] = comb.add %a, %a : i1 loc(#loc26) +// CHECK: %c = sv.wire {hw.verilogName = "c"} : !hw.inout loc(#loc27) +// CHECK: sv.assign %c, %[[v0]] : i1 loc(#loc15) +// CHECK: hw.output loc(#loc1) +// CHECK: } loc(#loc24) + +// CHECK: #loc = loc("{{.*}}verilog-locations.mlir{{.*}}) +// CHECK: #loc1 = loc("{{.*}}verilog-locations.mlir{{.*}}) +// CHECK: #loc2 = loc("":2:0) +// CHECK: #loc3 = loc("":8:9) +// CHECK: #loc5 = loc("{{.*}}verilog-locations.mlir{{.*}}) +// CHECK: #loc6 = loc("":6:2) +// CHECK: #loc7 = loc("":6:13) +// CHECK: #loc8 = loc("{{.*}}verilog-locations.mlir{{.*}}) +// CHECK: #loc9 = loc("{{.*}}verilog-locations.mlir{{.*}}) +// CHECK: #loc10 = loc("":7:11) +// CHECK: #loc11 = loc("":7:16) +// CHECK: #loc12 = loc("{{.*}}verilog-locations.mlir{{.*}}) +// CHECK: #loc13 = loc("":7:2) +// CHECK: #loc14 = loc("":7:17) +// CHECK: #loc15 = loc("{{.*}}verilog-locations.mlir{{.*}}) +// CHECK: #loc16 = loc(fused<"Range">[#loc2, #loc3]) +// CHECK: #loc17 = loc(fused<"Range">[#loc6, #loc7]) +// CHECK: #loc18 = loc(fused<"Range">[#loc10, #loc11]) +// CHECK: #loc19 = loc(fused<"Range">[#loc13, #loc14]) +// CHECK: #loc20 = loc(fused<"verilogLocations">[#loc16]) +// CHECK: #loc21 = loc(fused<"verilogLocations">[#loc17]) +// CHECK: #loc22 = loc(fused<"verilogLocations">[#loc18]) +// CHECK: #loc23 = loc(fused<"verilogLocations">[#loc19]) +// CHECK: #loc24 = loc(fused[#loc1, #loc20]) +// CHECK: #loc25 = loc(fused[#loc5, #loc21]) +// CHECK: #loc26 = loc(fused[#loc9, #loc22]) +// CHECK: #loc27 = loc(fused[#loc12, #loc23]) + +// ----- + + +module attributes {circt.loweringOptions = "emitVerilogLocations"} { +hw.module.extern @MyExtModule() +hw.module.extern @AParameterizedExtModule() +} +// CHECK: hw.module.extern @MyExtModule() loc(#loc11) +// CHECK: hw.module.extern @AParameterizedExtModule() loc(#loc12) + +// CHECK: #loc = loc("{{.*}}verilog-locations.mlir{{.*}}) +// CHECK: #loc1 = loc("{{.*}}verilog-locations.mlir{{.*}}) +// CHECK: #loc2 = loc("":2:0) +// CHECK: #loc3 = loc("":3:0) +// CHECK: #loc4 = loc("{{.*}}verilog-locations.mlir{{.*}}) +// CHECK: #loc5 = loc("":4:0) +// CHECK: #loc6 = loc("":5:0) +// CHECK: #loc7 = loc(fused<"Range">[#loc2, #loc3]) +// CHECK: #loc8 = loc(fused<"Range">[#loc5, #loc6]) +// CHECK: #loc9 = loc(fused<"verilogLocations">[#loc7]) +// CHECK: #loc10 = loc(fused<"verilogLocations">[#loc8]) +// CHECK: #loc11 = loc(fused[#loc1, #loc9]) +// CHECK: #loc12 = loc(fused[#loc4, #loc10])