CIRCT 22.0.0git
Loading...
Searching...
No Matches
ImportVerilog.cpp
Go to the documentation of this file.
1//===- ImportVerilog.cpp - Slang Verilog frontend integration -------------===//
2//
3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4// See https://llvm.org/LICENSE.txt for license information.
5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6//
7//===----------------------------------------------------------------------===//
8//
9// This implements bridging from the slang Verilog frontend to CIRCT dialects.
10//
11//===----------------------------------------------------------------------===//
12
14#include "mlir/IR/Diagnostics.h"
15#include "mlir/IR/Verifier.h"
16#include "mlir/Support/Timing.h"
17#include "mlir/Tools/mlir-translate/Translation.h"
18#include "llvm/ADT/Hashing.h"
19#include "llvm/Support/SourceMgr.h"
20
21#include "slang/diagnostics/DiagnosticClient.h"
22#include "slang/driver/Driver.h"
23#include "slang/parsing/Preprocessor.h"
24#include "slang/syntax/SyntaxPrinter.h"
25#include "slang/util/VersionInfo.h"
26
27using namespace mlir;
28using namespace circt;
29using namespace ImportVerilog;
30
31using llvm::SourceMgr;
32
34 std::string buffer;
35 llvm::raw_string_ostream os(buffer);
36 os << "slang version ";
37 os << slang::VersionInfo::getMajor() << ".";
38 os << slang::VersionInfo::getMinor() << ".";
39 os << slang::VersionInfo::getPatch() << "+";
40 os << slang::VersionInfo::getHash();
41 return buffer;
42}
43
44//===----------------------------------------------------------------------===//
45// Diagnostics
46//===----------------------------------------------------------------------===//
47
48/// Convert a slang `SourceLocation` to an MLIR `Location`.
49static Location convertLocation(MLIRContext *context,
50 const slang::SourceManager &sourceManager,
51 slang::SourceLocation loc) {
52 if (loc && loc.buffer() != slang::SourceLocation::NoLocation.buffer()) {
53 auto fileName = sourceManager.getFileName(loc);
54 auto line = sourceManager.getLineNumber(loc);
55 auto column = sourceManager.getColumnNumber(loc);
56 return FileLineColLoc::get(context, fileName, line, column);
57 }
58 return UnknownLoc::get(context);
59}
60
61Location Context::convertLocation(slang::SourceLocation loc) {
62 return ::convertLocation(getContext(), sourceManager, loc);
63}
64
65Location Context::convertLocation(slang::SourceRange range) {
66 return convertLocation(range.start());
67}
68
69namespace {
70/// A converter that can be plugged into a slang `DiagnosticEngine` as a client
71/// that will map slang diagnostics to their MLIR counterpart and emit them.
72class MlirDiagnosticClient : public slang::DiagnosticClient {
73public:
74 MlirDiagnosticClient(MLIRContext *context) : context(context) {}
75
76 void report(const slang::ReportedDiagnostic &diag) override {
77 // Generate the primary MLIR diagnostic.
78 auto &diagEngine = context->getDiagEngine();
79 auto mlirDiag = diagEngine.emit(convertLocation(diag.location),
80 getSeverity(diag.severity));
81 mlirDiag << diag.formattedMessage;
82
83 // Append the name of the option that can be used to control this
84 // diagnostic.
85 auto optionName = engine->getOptionName(diag.originalDiagnostic.code);
86 if (!optionName.empty())
87 mlirDiag << " [-W" << optionName << "]";
88
89 // Write out macro expansions, if we have any, in reverse order.
90 for (auto loc : std::views::reverse(diag.expansionLocs)) {
91 auto &note = mlirDiag.attachNote(
92 convertLocation(sourceManager->getFullyOriginalLoc(loc)));
93 auto macroName = sourceManager->getMacroName(loc);
94 if (macroName.empty())
95 note << "expanded from here";
96 else
97 note << "expanded from macro '" << macroName << "'";
98 }
99
100 // Write out the include stack.
101 slang::SmallVector<slang::SourceLocation> includeStack;
102 getIncludeStack(diag.location.buffer(), includeStack);
103 for (auto &loc : std::views::reverse(includeStack))
104 mlirDiag.attachNote(convertLocation(loc)) << "included from here";
105 }
106
107 /// Convert a slang `SourceLocation` to an MLIR `Location`.
108 Location convertLocation(slang::SourceLocation loc) const {
109 return ::convertLocation(context, *sourceManager, loc);
110 }
111
112 static DiagnosticSeverity getSeverity(slang::DiagnosticSeverity severity) {
113 switch (severity) {
114 case slang::DiagnosticSeverity::Fatal:
115 case slang::DiagnosticSeverity::Error:
116 return DiagnosticSeverity::Error;
117 case slang::DiagnosticSeverity::Warning:
118 return DiagnosticSeverity::Warning;
119 case slang::DiagnosticSeverity::Ignored:
120 case slang::DiagnosticSeverity::Note:
121 return DiagnosticSeverity::Remark;
122 }
123 llvm_unreachable("all slang diagnostic severities should be handled");
124 return DiagnosticSeverity::Error;
125 }
126
127private:
128 MLIRContext *context;
129};
130} // namespace
131
132// Allow for `slang::BufferID` to be used as hash map keys.
133namespace llvm {
134template <>
135struct DenseMapInfo<slang::BufferID> {
136 static slang::BufferID getEmptyKey() { return slang::BufferID(); }
137 static slang::BufferID getTombstoneKey() {
138 return slang::BufferID(UINT32_MAX - 1, ""sv);
139 // UINT32_MAX is already used by `BufferID::getPlaceholder`.
140 }
141 static unsigned getHashValue(slang::BufferID id) {
142 return llvm::hash_value(id.getId());
143 }
144 static bool isEqual(slang::BufferID a, slang::BufferID b) { return a == b; }
145};
146} // namespace llvm
147
148//===----------------------------------------------------------------------===//
149// Driver
150//===----------------------------------------------------------------------===//
151
152namespace {
153const static ImportVerilogOptions defaultOptions;
154
155struct ImportDriver {
156 ImportDriver(MLIRContext *mlirContext, TimingScope &ts,
157 const ImportVerilogOptions *options)
158 : mlirContext(mlirContext), ts(ts),
159 options(options ? *options : defaultOptions) {}
160
161 LogicalResult prepareDriver(SourceMgr &sourceMgr);
162 LogicalResult importVerilog(ModuleOp module);
163 LogicalResult preprocessVerilog(llvm::raw_ostream &os);
164
165 MLIRContext *mlirContext;
166 TimingScope &ts;
167 const ImportVerilogOptions &options;
168
169 // Use slang's driver which conveniently packages a lot of the things we
170 // need for compilation.
171 slang::driver::Driver driver;
172};
173} // namespace
174
175/// Populate the Slang driver with source files from the given `sourceMgr`, and
176/// configure driver options based on the `ImportVerilogOptions` passed to the
177/// `ImportDriver` constructor.
178LogicalResult ImportDriver::prepareDriver(SourceMgr &sourceMgr) {
179 // Use slang's driver which conveniently packages a lot of the things we
180 // need for compilation.
181 auto diagClient = std::make_shared<MlirDiagnosticClient>(mlirContext);
182 driver.diagEngine.addClient(diagClient);
183
184 for (const auto &value : options.commandFiles)
185 if (!driver.processCommandFiles(value, /*makeRelative=*/true,
186 /*separateUnit=*/true))
187 return failure();
188
189 // Populate the source manager with the source files.
190 // NOTE: This is a bit ugly since we're essentially copying the Verilog
191 // source text in memory. At a later stage we'll want to extend slang's
192 // SourceManager such that it can contain non-owned buffers. This will do
193 // for now.
194 DenseSet<StringRef> seenBuffers;
195 for (unsigned i = 0, e = sourceMgr.getNumBuffers(); i < e; ++i) {
196 const llvm::MemoryBuffer *mlirBuffer = sourceMgr.getMemoryBuffer(i + 1);
197 auto name = mlirBuffer->getBufferIdentifier();
198 if (!name.empty() && !seenBuffers.insert(name).second)
199 continue; // Slang doesn't like listing the same buffer twice
200 auto slangBuffer =
201 driver.sourceManager.assignText(name, mlirBuffer->getBuffer());
202 driver.sourceLoader.addBuffer(slangBuffer);
203 }
204
205 for (const auto &libDir : options.libDirs)
206 driver.sourceLoader.addSearchDirectories(libDir);
207
208 for (const auto &libExt : options.libExts)
209 driver.sourceLoader.addSearchExtension(libExt);
210
211 for (const auto &includeDir : options.includeDirs)
212 if (driver.sourceManager.addUserDirectories(includeDir))
213 return failure();
214
215 for (const auto &includeSystemDir : options.includeSystemDirs)
216 if (driver.sourceManager.addSystemDirectories(includeSystemDir))
217 return failure();
218
219 // Populate the driver options.
220 driver.options.excludeExts.insert(options.excludeExts.begin(),
221 options.excludeExts.end());
222 driver.options.ignoreDirectives = options.ignoreDirectives;
223
224 driver.options.maxIncludeDepth = options.maxIncludeDepth;
225 driver.options.defines = options.defines;
226 driver.options.undefines = options.undefines;
227 driver.options.librariesInheritMacros = options.librariesInheritMacros;
228
229 driver.options.timeScale = options.timeScale;
230 driver.options.compilationFlags.emplace(
231 slang::ast::CompilationFlags::AllowUseBeforeDeclare,
232 options.allowUseBeforeDeclare);
233 driver.options.compilationFlags.emplace(
234 slang::ast::CompilationFlags::IgnoreUnknownModules,
235 options.ignoreUnknownModules);
236 driver.options.compilationFlags.emplace(
237 slang::ast::CompilationFlags::LintMode,
239 driver.options.compilationFlags.emplace(
240 slang::ast::CompilationFlags::DisableInstanceCaching, false);
241 driver.options.topModules = options.topModules;
242 driver.options.paramOverrides = options.paramOverrides;
243
244 driver.options.errorLimit = options.errorLimit;
245 driver.options.warningOptions = options.warningOptions;
246
247 driver.options.singleUnit = options.singleUnit;
248
249 return success(driver.processOptions());
250}
251
252/// Parse and elaborate the prepared source files, and populate the given MLIR
253/// `module` with corresponding operations.
254LogicalResult ImportDriver::importVerilog(ModuleOp module) {
255 // Parse the input.
256 auto parseTimer = ts.nest("Verilog parser");
257 bool parseSuccess = driver.parseAllSources();
258 parseTimer.stop();
259
260 // Elaborate the input.
261 auto compileTimer = ts.nest("Verilog elaboration");
262 auto compilation = driver.createCompilation();
263 for (auto &diag : compilation->getAllDiagnostics())
264 driver.diagEngine.issue(diag);
265 if (!parseSuccess || driver.diagEngine.getNumErrors() > 0)
266 return failure();
267 compileTimer.stop();
268
269 // If we were only supposed to lint the input, return here. This leaves the
270 // module empty, but any Slang linting messages got reported as diagnostics.
271 if (options.mode == ImportVerilogOptions::Mode::OnlyLint)
272 return success();
273
274 // Traverse the parsed Verilog AST and map it to the equivalent CIRCT ops.
275 mlirContext
276 ->loadDialect<moore::MooreDialect, hw::HWDialect, cf::ControlFlowDialect,
277 func::FuncDialect, verif::VerifDialect, ltl::LTLDialect,
278 debug::DebugDialect>();
279 auto conversionTimer = ts.nest("Verilog to dialect mapping");
280 Context context(options, *compilation, module, driver.sourceManager);
281 if (failed(context.convertCompilation()))
282 return failure();
283 conversionTimer.stop();
284
285 // Run the verifier on the constructed module to ensure it is clean.
286 auto verifierTimer = ts.nest("Post-parse verification");
287 return verify(module);
288}
289
290/// Preprocess the prepared source files and print them to the given output
291/// stream.
292LogicalResult ImportDriver::preprocessVerilog(llvm::raw_ostream &os) {
293 auto parseTimer = ts.nest("Verilog preprocessing");
294
295 // Run the preprocessor to completion across all sources previously added with
296 // `pushSource`, report diagnostics, and print the output.
297 auto preprocessAndPrint = [&](slang::parsing::Preprocessor &preprocessor) {
298 slang::syntax::SyntaxPrinter output;
299 output.setIncludeComments(false);
300 while (true) {
301 slang::parsing::Token token = preprocessor.next();
302 output.print(token);
303 if (token.kind == slang::parsing::TokenKind::EndOfFile)
304 break;
305 }
306
307 for (auto &diag : preprocessor.getDiagnostics()) {
308 if (diag.isError()) {
309 driver.diagEngine.issue(diag);
310 return failure();
311 }
312 }
313 os << output.str();
314 return success();
315 };
316
317 // Depending on whether the single-unit option is set, either add all source
318 // files to a single preprocessor such that they share define macros and
319 // directives, or create a separate preprocessor for each, such that each
320 // source file is in its own compilation unit.
321 auto optionBag = driver.createOptionBag();
322 if (driver.options.singleUnit == true) {
323 slang::BumpAllocator alloc;
324 slang::Diagnostics diagnostics;
325 slang::parsing::Preprocessor preprocessor(driver.sourceManager, alloc,
326 diagnostics, optionBag);
327 // Sources have to be pushed in reverse, as they form a stack in the
328 // preprocessor. Last pushed source is processed first.
329 auto sources = driver.sourceLoader.loadSources();
330 for (auto &buffer : std::views::reverse(sources))
331 preprocessor.pushSource(buffer);
332 if (failed(preprocessAndPrint(preprocessor)))
333 return failure();
334 } else {
335 for (auto &buffer : driver.sourceLoader.loadSources()) {
336 slang::BumpAllocator alloc;
337 slang::Diagnostics diagnostics;
338 slang::parsing::Preprocessor preprocessor(driver.sourceManager, alloc,
339 diagnostics, optionBag);
340 preprocessor.pushSource(buffer);
341 if (failed(preprocessAndPrint(preprocessor)))
342 return failure();
343 }
344 }
345
346 return success();
347}
348
349//===----------------------------------------------------------------------===//
350// Entry Points
351//===----------------------------------------------------------------------===//
352
353/// Parse the specified Verilog inputs into the specified MLIR context.
354LogicalResult circt::importVerilog(SourceMgr &sourceMgr,
355 MLIRContext *mlirContext, TimingScope &ts,
356 ModuleOp module,
357 const ImportVerilogOptions *options) {
358 ImportDriver importDriver(mlirContext, ts, options);
359 if (failed(importDriver.prepareDriver(sourceMgr)))
360 return failure();
361 return importDriver.importVerilog(module);
362}
363
364/// Run the files in a source manager through Slang's Verilog preprocessor and
365/// emit the result to the given output stream.
366LogicalResult circt::preprocessVerilog(SourceMgr &sourceMgr,
367 MLIRContext *mlirContext,
368 TimingScope &ts, llvm::raw_ostream &os,
369 const ImportVerilogOptions *options) {
370 ImportDriver importDriver(mlirContext, ts, options);
371 if (failed(importDriver.prepareDriver(sourceMgr)))
372 return failure();
373 return importDriver.preprocessVerilog(os);
374}
375
376/// Entry point as an MLIR translation.
378 static TranslateToMLIRRegistration fromVerilog(
379 "import-verilog", "import Verilog or SystemVerilog",
380 [](llvm::SourceMgr &sourceMgr, MLIRContext *context) {
381 TimingScope ts;
383 ModuleOp::create(UnknownLoc::get(context)));
384 ImportVerilogOptions options;
385 options.debugInfo = true;
386 options.warningOptions.push_back("no-missing-top");
387 if (failed(
388 importVerilog(sourceMgr, context, ts, module.get(), &options)))
389 module = {};
390 return module;
391 });
392}
static Location convertLocation(MLIRContext *context, const slang::SourceManager &sourceManager, slang::SourceLocation loc)
Convert a slang SourceLocation to an MLIR Location.
static llvm::lsp::DiagnosticSeverity getSeverity(slang::DiagnosticSeverity severity)
The InstanceGraph op interface, see InstanceGraphInterface.td for more details.
std::string getSlangVersion()
Return a human-readable string describing the slang frontend version linked into CIRCT.
mlir::LogicalResult importVerilog(llvm::SourceMgr &sourceMgr, mlir::MLIRContext *context, mlir::TimingScope &ts, mlir::ModuleOp module, const ImportVerilogOptions *options=nullptr)
Parse files in a source manager as Verilog source code and populate the given MLIR module with corres...
mlir::LogicalResult preprocessVerilog(llvm::SourceMgr &sourceMgr, mlir::MLIRContext *context, mlir::TimingScope &ts, llvm::raw_ostream &os, const ImportVerilogOptions *options=nullptr)
Run the files in a source manager through Slang's Verilog preprocessor and emit the result to the giv...
void registerFromVerilogTranslation()
Register the import-verilog MLIR translation.
Definition sv.py:1
Options that control how Verilog input files are parsed and processed.
std::vector< std::string > warningOptions
A list of warning options that will be passed to the DiagnosticEngine.
@ OnlyLint
Only lint the input, without elaboration and lowering to CIRCT IR.
bool debugInfo
Generate debug information in the form of debug dialect ops in the IR.
A helper class to facilitate the conversion from a Slang AST to MLIR operations.
const slang::SourceManager & sourceManager
MLIRContext * getContext()
Return the MLIR context.
Location convertLocation(slang::SourceLocation loc)
Convert a slang SourceLocation into an MLIR Location.
static bool isEqual(slang::BufferID a, slang::BufferID b)
static slang::BufferID getTombstoneKey()
static unsigned getHashValue(slang::BufferID id)