CIRCT 20.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/BuiltinTypes.h"
15#include "mlir/IR/Diagnostics.h"
16#include "mlir/IR/Verifier.h"
17#include "mlir/Support/Timing.h"
18#include "mlir/Tools/mlir-translate/Translation.h"
19#include "llvm/ADT/Hashing.h"
20#include "llvm/Support/SourceMgr.h"
21
22#include "slang/diagnostics/DiagnosticClient.h"
23#include "slang/driver/Driver.h"
24#include "slang/parsing/Preprocessor.h"
25#include "slang/syntax/SyntaxPrinter.h"
26#include "slang/util/Version.h"
27
28using namespace mlir;
29using namespace circt;
30using namespace ImportVerilog;
31
32using llvm::SourceMgr;
33
35 std::string buffer;
36 llvm::raw_string_ostream os(buffer);
37 os << "slang version ";
38 os << slang::VersionInfo::getMajor() << ".";
39 os << slang::VersionInfo::getMinor() << ".";
40 os << slang::VersionInfo::getPatch() << "+";
41 os << slang::VersionInfo::getHash();
42 return buffer;
43}
44
45//===----------------------------------------------------------------------===//
46// Diagnostics
47//===----------------------------------------------------------------------===//
48
49/// Convert a slang `SourceLocation` to an MLIR `Location`.
50static Location
51convertLocation(MLIRContext *context, const slang::SourceManager &sourceManager,
53 slang::SourceLocation loc) {
54 if (loc && loc.buffer() != slang::SourceLocation::NoLocation.buffer()) {
55 auto fileName = bufferFilePaths.lookup(loc.buffer());
56 auto line = sourceManager.getLineNumber(loc);
57 auto column = sourceManager.getColumnNumber(loc);
58 return FileLineColLoc::get(context, fileName, line, column);
59 }
60 return UnknownLoc::get(context);
61}
62
63Location Context::convertLocation(slang::SourceLocation loc) {
64 return ::convertLocation(getContext(), sourceManager, bufferFilePaths, loc);
65}
66
67Location Context::convertLocation(slang::SourceRange range) {
68 return convertLocation(range.start());
69}
70
71namespace {
72/// A converter that can be plugged into a slang `DiagnosticEngine` as a client
73/// that will map slang diagnostics to their MLIR counterpart and emit them.
74class MlirDiagnosticClient : public slang::DiagnosticClient {
75public:
76 MlirDiagnosticClient(
77 MLIRContext *context,
79 : context(context), bufferFilePaths(bufferFilePaths) {}
80
81 void report(const slang::ReportedDiagnostic &diag) override {
82 // Generate the primary MLIR diagnostic.
83 auto &diagEngine = context->getDiagEngine();
84 auto mlirDiag = diagEngine.emit(convertLocation(diag.location),
85 getSeverity(diag.severity));
86 mlirDiag << diag.formattedMessage;
87
88 // Append the name of the option that can be used to control this
89 // diagnostic.
90 auto optionName = engine->getOptionName(diag.originalDiagnostic.code);
91 if (!optionName.empty())
92 mlirDiag << " [-W" << optionName << "]";
93
94 // Write out macro expansions, if we have any, in reverse order.
95 for (auto it = diag.expansionLocs.rbegin(); it != diag.expansionLocs.rend();
96 it++) {
97 auto &note = mlirDiag.attachNote(
98 convertLocation(sourceManager->getFullyOriginalLoc(*it)));
99 auto macroName = sourceManager->getMacroName(*it);
100 if (macroName.empty())
101 note << "expanded from here";
102 else
103 note << "expanded from macro '" << macroName << "'";
104 }
105 }
106
107 /// Convert a slang `SourceLocation` to an MLIR `Location`.
108 Location convertLocation(slang::SourceLocation loc) const {
109 return ::convertLocation(context, *sourceManager, bufferFilePaths, 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;
130};
131} // namespace
132
133// Allow for `slang::BufferID` to be used as hash map keys.
134namespace llvm {
135template <>
136struct DenseMapInfo<slang::BufferID> {
137 static slang::BufferID getEmptyKey() { return slang::BufferID(); }
138 static slang::BufferID getTombstoneKey() {
139 return slang::BufferID(UINT32_MAX - 1, ""sv);
140 // UINT32_MAX is already used by `BufferID::getPlaceholder`.
141 }
142 static unsigned getHashValue(slang::BufferID id) {
143 return llvm::hash_value(id.getId());
144 }
145 static bool isEqual(slang::BufferID a, slang::BufferID b) { return a == b; }
146};
147} // namespace llvm
148
149//===----------------------------------------------------------------------===//
150// Driver
151//===----------------------------------------------------------------------===//
152
153namespace {
154const static ImportVerilogOptions defaultOptions;
155
156struct ImportDriver {
157 ImportDriver(MLIRContext *mlirContext, TimingScope &ts,
158 const ImportVerilogOptions *options)
159 : mlirContext(mlirContext), ts(ts),
160 options(options ? *options : defaultOptions) {}
161
162 LogicalResult prepareDriver(SourceMgr &sourceMgr);
163 LogicalResult importVerilog(ModuleOp module);
164 LogicalResult preprocessVerilog(llvm::raw_ostream &os);
165
166 MLIRContext *mlirContext;
167 TimingScope &ts;
168 const ImportVerilogOptions &options;
169
170 // Use slang's driver which conveniently packages a lot of the things we
171 // need for compilation.
172 slang::driver::Driver driver;
173
174 // A separate mapping from slang's buffers to the original MLIR file name
175 // since slang's `SourceLocation::getFileName` returns a modified version that
176 // is nice for human consumption (proximate paths, just file names, etc.), but
177 // breaks MLIR's assumption that the diagnostics report the exact file paths
178 // that appear in the `SourceMgr`. We use this separate map to lookup the
179 // exact paths and use those for reporting.
180 //
181 // See: https://github.com/MikePopoloski/slang/discussions/658
183};
184} // namespace
185
186/// Populate the Slang driver with source files from the given `sourceMgr`, and
187/// configure driver options based on the `ImportVerilogOptions` passed to the
188/// `ImportDriver` constructor.
189LogicalResult ImportDriver::prepareDriver(SourceMgr &sourceMgr) {
190 // Use slang's driver which conveniently packages a lot of the things we
191 // need for compilation.
192 auto diagClient =
193 std::make_shared<MlirDiagnosticClient>(mlirContext, bufferFilePaths);
194 driver.diagEngine.addClient(diagClient);
195
196 // Populate the source manager with the source files.
197 // NOTE: This is a bit ugly since we're essentially copying the Verilog
198 // source text in memory. At a later stage we'll want to extend slang's
199 // SourceManager such that it can contain non-owned buffers. This will do
200 // for now.
201 for (unsigned i = 0, e = sourceMgr.getNumBuffers(); i < e; ++i) {
202 const llvm::MemoryBuffer *mlirBuffer = sourceMgr.getMemoryBuffer(i + 1);
203 auto slangBuffer = driver.sourceManager.assignText(
204 mlirBuffer->getBufferIdentifier(), mlirBuffer->getBuffer());
205 driver.buffers.push_back(slangBuffer);
206 bufferFilePaths.insert({slangBuffer.id, mlirBuffer->getBufferIdentifier()});
207 }
208
209 // Populate the driver options.
210 driver.options.includeDirs = options.includeDirs;
211 driver.options.includeSystemDirs = options.includeSystemDirs;
212 driver.options.libDirs = options.libDirs;
213 driver.options.libExts = options.libExts;
214 driver.options.excludeExts.insert(options.excludeExts.begin(),
215 options.excludeExts.end());
216 driver.options.ignoreDirectives = options.ignoreDirectives;
217
218 driver.options.maxIncludeDepth = options.maxIncludeDepth;
219 driver.options.defines = options.defines;
220 driver.options.undefines = options.undefines;
221 driver.options.librariesInheritMacros = options.librariesInheritMacros;
222
223 driver.options.timeScale = options.timeScale;
224 driver.options.allowUseBeforeDeclare = options.allowUseBeforeDeclare;
225 driver.options.ignoreUnknownModules = options.ignoreUnknownModules;
226 driver.options.onlyLint =
228 driver.options.topModules = options.topModules;
229 driver.options.paramOverrides = options.paramOverrides;
230
231 driver.options.errorLimit = options.errorLimit;
232 driver.options.warningOptions = options.warningOptions;
233 driver.options.suppressWarningsPaths = options.suppressWarningsPaths;
234
235 driver.options.singleUnit = options.singleUnit;
236 driver.options.libraryFiles = options.libraryFiles;
237
238 for (auto &dir : sourceMgr.getIncludeDirs())
239 driver.options.includeDirs.push_back(dir);
240
241 return success(driver.processOptions());
242}
243
244/// Parse and elaborate the prepared source files, and populate the given MLIR
245/// `module` with corresponding operations.
246LogicalResult ImportDriver::importVerilog(ModuleOp module) {
247 // Parse the input.
248 auto parseTimer = ts.nest("Verilog parser");
249 bool parseSuccess = driver.parseAllSources();
250 parseTimer.stop();
251
252 // Elaborate the input.
253 auto compileTimer = ts.nest("Verilog elaboration");
254 auto compilation = driver.createCompilation();
255 for (auto &diag : compilation->getAllDiagnostics())
256 driver.diagEngine.issue(diag);
257 if (!parseSuccess || driver.diagEngine.getNumErrors() > 0)
258 return failure();
259 compileTimer.stop();
260
261 // If we were only supposed to lint the input, return here. This leaves the
262 // module empty, but any Slang linting messages got reported as diagnostics.
263 if (options.mode == ImportVerilogOptions::Mode::OnlyLint)
264 return success();
265
266 // Traverse the parsed Verilog AST and map it to the equivalent CIRCT ops.
267 mlirContext
268 ->loadDialect<moore::MooreDialect, hw::HWDialect, cf::ControlFlowDialect,
269 func::FuncDialect, debug::DebugDialect>();
270 auto conversionTimer = ts.nest("Verilog to dialect mapping");
271 Context context(options, *compilation, module, driver.sourceManager,
272 bufferFilePaths);
273 if (failed(context.convertCompilation()))
274 return failure();
275 conversionTimer.stop();
276
277 // Run the verifier on the constructed module to ensure it is clean.
278 auto verifierTimer = ts.nest("Post-parse verification");
279 return verify(module);
280}
281
282/// Preprocess the prepared source files and print them to the given output
283/// stream.
284LogicalResult ImportDriver::preprocessVerilog(llvm::raw_ostream &os) {
285 auto parseTimer = ts.nest("Verilog preprocessing");
286
287 // Run the preprocessor to completion across all sources previously added with
288 // `pushSource`, report diagnostics, and print the output.
289 auto preprocessAndPrint = [&](slang::parsing::Preprocessor &preprocessor) {
290 slang::syntax::SyntaxPrinter output;
291 output.setIncludeComments(false);
292 while (true) {
293 slang::parsing::Token token = preprocessor.next();
294 output.print(token);
295 if (token.kind == slang::parsing::TokenKind::EndOfFile)
296 break;
297 }
298
299 for (auto &diag : preprocessor.getDiagnostics()) {
300 if (diag.isError()) {
301 driver.diagEngine.issue(diag);
302 return failure();
303 }
304 }
305 os << output.str();
306 return success();
307 };
308
309 // Depending on whether the single-unit option is set, either add all source
310 // files to a single preprocessor such that they share define macros and
311 // directives, or create a separate preprocessor for each, such that each
312 // source file is in its own compilation unit.
313 auto optionBag = driver.createOptionBag();
314 if (driver.options.singleUnit == true) {
315 slang::BumpAllocator alloc;
316 slang::Diagnostics diagnostics;
317 slang::parsing::Preprocessor preprocessor(driver.sourceManager, alloc,
318 diagnostics, optionBag);
319 // Sources have to be pushed in reverse, as they form a stack in the
320 // preprocessor. Last pushed source is processed first.
321 for (auto &buffer : slang::make_reverse_range(driver.buffers))
322 preprocessor.pushSource(buffer);
323 if (failed(preprocessAndPrint(preprocessor)))
324 return failure();
325 } else {
326 for (auto &buffer : driver.buffers) {
327 slang::BumpAllocator alloc;
328 slang::Diagnostics diagnostics;
329 slang::parsing::Preprocessor preprocessor(driver.sourceManager, alloc,
330 diagnostics, optionBag);
331 preprocessor.pushSource(buffer);
332 if (failed(preprocessAndPrint(preprocessor)))
333 return failure();
334 }
335 }
336
337 return success();
338}
339
340//===----------------------------------------------------------------------===//
341// Entry Points
342//===----------------------------------------------------------------------===//
343
344/// Execute a callback and report any thrown exceptions as "internal slang
345/// error" MLIR diagnostics.
346static LogicalResult
347catchExceptions(llvm::function_ref<LogicalResult()> callback) {
348 try {
349 return callback();
350 } catch (const std::exception &e) {
351 return emitError(UnknownLoc(), "internal slang error: ") << e.what();
352 }
353}
354
355/// Parse the specified Verilog inputs into the specified MLIR context.
356LogicalResult circt::importVerilog(SourceMgr &sourceMgr,
357 MLIRContext *mlirContext, TimingScope &ts,
358 ModuleOp module,
359 const ImportVerilogOptions *options) {
360 return catchExceptions([&] {
361 ImportDriver importDriver(mlirContext, ts, options);
362 if (failed(importDriver.prepareDriver(sourceMgr)))
363 return failure();
364 return importDriver.importVerilog(module);
365 });
366}
367
368/// Run the files in a source manager through Slang's Verilog preprocessor and
369/// emit the result to the given output stream.
370LogicalResult circt::preprocessVerilog(SourceMgr &sourceMgr,
371 MLIRContext *mlirContext,
372 TimingScope &ts, llvm::raw_ostream &os,
373 const ImportVerilogOptions *options) {
374 return catchExceptions([&] {
375 ImportDriver importDriver(mlirContext, ts, options);
376 if (failed(importDriver.prepareDriver(sourceMgr)))
377 return failure();
378 return importDriver.preprocessVerilog(os);
379 });
380}
381
382/// Entry point as an MLIR translation.
384 static TranslateToMLIRRegistration fromVerilog(
385 "import-verilog", "import Verilog or SystemVerilog",
386 [](llvm::SourceMgr &sourceMgr, MLIRContext *context) {
387 TimingScope ts;
389 ModuleOp::create(UnknownLoc::get(context)));
390 ImportVerilogOptions options;
391 options.debugInfo = true;
392 if (failed(
393 importVerilog(sourceMgr, context, ts, module.get(), &options)))
394 module = {};
395 return module;
396 });
397}
static LogicalResult catchExceptions(llvm::function_ref< LogicalResult()> callback)
Execute a callback and report any thrown exceptions as "internal slang error" MLIR diagnostics.
static Location convertLocation(MLIRContext *context, const slang::SourceManager &sourceManager, SmallDenseMap< slang::BufferID, StringRef > &bufferFilePaths, slang::SourceLocation loc)
Convert a slang SourceLocation to an MLIR Location.
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.
@ 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.
SmallDenseMap< slang::BufferID, StringRef > & bufferFilePaths
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)