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 // Populate the source manager with the source files.
185 // NOTE: This is a bit ugly since we're essentially copying the Verilog
186 // source text in memory. At a later stage we'll want to extend slang's
187 // SourceManager such that it can contain non-owned buffers. This will do
188 // for now.
189 DenseSet<StringRef> seenBuffers;
190 for (unsigned i = 0, e = sourceMgr.getNumBuffers(); i < e; ++i) {
191 const llvm::MemoryBuffer *mlirBuffer = sourceMgr.getMemoryBuffer(i + 1);
192 auto name = mlirBuffer->getBufferIdentifier();
193 if (!name.empty() && !seenBuffers.insert(name).second)
194 continue; // Slang doesn't like listing the same buffer twice
195 auto slangBuffer =
196 driver.sourceManager.assignText(name, mlirBuffer->getBuffer());
197 driver.sourceLoader.addBuffer(slangBuffer);
198 }
199
200 for (const auto &libDir : options.libDirs)
201 driver.sourceLoader.addSearchDirectories(libDir);
202
203 for (const auto &libExt : options.libExts)
204 driver.sourceLoader.addSearchExtension(libExt);
205
206 for (const auto &includeDir : options.includeDirs)
207 if (driver.sourceManager.addUserDirectories(includeDir))
208 return failure();
209
210 for (const auto &includeSystemDir : options.includeSystemDirs)
211 if (driver.sourceManager.addSystemDirectories(includeSystemDir))
212 return failure();
213
214 // Populate the driver options.
215 driver.options.excludeExts.insert(options.excludeExts.begin(),
216 options.excludeExts.end());
217 driver.options.ignoreDirectives = options.ignoreDirectives;
218
219 driver.options.maxIncludeDepth = options.maxIncludeDepth;
220 driver.options.defines = options.defines;
221 driver.options.undefines = options.undefines;
222 driver.options.librariesInheritMacros = options.librariesInheritMacros;
223
224 driver.options.timeScale = options.timeScale;
225 driver.options.compilationFlags.emplace(
226 slang::ast::CompilationFlags::AllowUseBeforeDeclare,
227 options.allowUseBeforeDeclare);
228 driver.options.compilationFlags.emplace(
229 slang::ast::CompilationFlags::IgnoreUnknownModules,
230 options.ignoreUnknownModules);
231 driver.options.compilationFlags.emplace(
232 slang::ast::CompilationFlags::LintMode,
234 driver.options.compilationFlags.emplace(
235 slang::ast::CompilationFlags::DisableInstanceCaching, false);
236 driver.options.topModules = options.topModules;
237 driver.options.paramOverrides = options.paramOverrides;
238
239 driver.options.errorLimit = options.errorLimit;
240 driver.options.warningOptions = options.warningOptions;
241
242 driver.options.singleUnit = options.singleUnit;
243
244 return success(driver.processOptions());
245}
246
247/// Parse and elaborate the prepared source files, and populate the given MLIR
248/// `module` with corresponding operations.
249LogicalResult ImportDriver::importVerilog(ModuleOp module) {
250 // Parse the input.
251 auto parseTimer = ts.nest("Verilog parser");
252 bool parseSuccess = driver.parseAllSources();
253 parseTimer.stop();
254
255 // Elaborate the input.
256 auto compileTimer = ts.nest("Verilog elaboration");
257 auto compilation = driver.createCompilation();
258 for (auto &diag : compilation->getAllDiagnostics())
259 driver.diagEngine.issue(diag);
260 if (!parseSuccess || driver.diagEngine.getNumErrors() > 0)
261 return failure();
262 compileTimer.stop();
263
264 // If we were only supposed to lint the input, return here. This leaves the
265 // module empty, but any Slang linting messages got reported as diagnostics.
266 if (options.mode == ImportVerilogOptions::Mode::OnlyLint)
267 return success();
268
269 // Traverse the parsed Verilog AST and map it to the equivalent CIRCT ops.
270 mlirContext
271 ->loadDialect<moore::MooreDialect, hw::HWDialect, cf::ControlFlowDialect,
272 func::FuncDialect, verif::VerifDialect, ltl::LTLDialect,
273 debug::DebugDialect>();
274 auto conversionTimer = ts.nest("Verilog to dialect mapping");
275 Context context(options, *compilation, module, driver.sourceManager);
276 if (failed(context.convertCompilation()))
277 return failure();
278 conversionTimer.stop();
279
280 // Run the verifier on the constructed module to ensure it is clean.
281 auto verifierTimer = ts.nest("Post-parse verification");
282 return verify(module);
283}
284
285/// Preprocess the prepared source files and print them to the given output
286/// stream.
287LogicalResult ImportDriver::preprocessVerilog(llvm::raw_ostream &os) {
288 auto parseTimer = ts.nest("Verilog preprocessing");
289
290 // Run the preprocessor to completion across all sources previously added with
291 // `pushSource`, report diagnostics, and print the output.
292 auto preprocessAndPrint = [&](slang::parsing::Preprocessor &preprocessor) {
293 slang::syntax::SyntaxPrinter output;
294 output.setIncludeComments(false);
295 while (true) {
296 slang::parsing::Token token = preprocessor.next();
297 output.print(token);
298 if (token.kind == slang::parsing::TokenKind::EndOfFile)
299 break;
300 }
301
302 for (auto &diag : preprocessor.getDiagnostics()) {
303 if (diag.isError()) {
304 driver.diagEngine.issue(diag);
305 return failure();
306 }
307 }
308 os << output.str();
309 return success();
310 };
311
312 // Depending on whether the single-unit option is set, either add all source
313 // files to a single preprocessor such that they share define macros and
314 // directives, or create a separate preprocessor for each, such that each
315 // source file is in its own compilation unit.
316 auto optionBag = driver.createOptionBag();
317 if (driver.options.singleUnit == true) {
318 slang::BumpAllocator alloc;
319 slang::Diagnostics diagnostics;
320 slang::parsing::Preprocessor preprocessor(driver.sourceManager, alloc,
321 diagnostics, optionBag);
322 // Sources have to be pushed in reverse, as they form a stack in the
323 // preprocessor. Last pushed source is processed first.
324 auto sources = driver.sourceLoader.loadSources();
325 for (auto &buffer : std::views::reverse(sources))
326 preprocessor.pushSource(buffer);
327 if (failed(preprocessAndPrint(preprocessor)))
328 return failure();
329 } else {
330 for (auto &buffer : driver.sourceLoader.loadSources()) {
331 slang::BumpAllocator alloc;
332 slang::Diagnostics diagnostics;
333 slang::parsing::Preprocessor preprocessor(driver.sourceManager, alloc,
334 diagnostics, optionBag);
335 preprocessor.pushSource(buffer);
336 if (failed(preprocessAndPrint(preprocessor)))
337 return failure();
338 }
339 }
340
341 return success();
342}
343
344//===----------------------------------------------------------------------===//
345// Entry Points
346//===----------------------------------------------------------------------===//
347
348/// Parse the specified Verilog inputs into the specified MLIR context.
349LogicalResult circt::importVerilog(SourceMgr &sourceMgr,
350 MLIRContext *mlirContext, TimingScope &ts,
351 ModuleOp module,
352 const ImportVerilogOptions *options) {
353 ImportDriver importDriver(mlirContext, ts, options);
354 if (failed(importDriver.prepareDriver(sourceMgr)))
355 return failure();
356 return importDriver.importVerilog(module);
357}
358
359/// Run the files in a source manager through Slang's Verilog preprocessor and
360/// emit the result to the given output stream.
361LogicalResult circt::preprocessVerilog(SourceMgr &sourceMgr,
362 MLIRContext *mlirContext,
363 TimingScope &ts, llvm::raw_ostream &os,
364 const ImportVerilogOptions *options) {
365 ImportDriver importDriver(mlirContext, ts, options);
366 if (failed(importDriver.prepareDriver(sourceMgr)))
367 return failure();
368 return importDriver.preprocessVerilog(os);
369}
370
371/// Entry point as an MLIR translation.
373 static TranslateToMLIRRegistration fromVerilog(
374 "import-verilog", "import Verilog or SystemVerilog",
375 [](llvm::SourceMgr &sourceMgr, MLIRContext *context) {
376 TimingScope ts;
378 ModuleOp::create(UnknownLoc::get(context)));
379 ImportVerilogOptions options;
380 options.debugInfo = true;
381 options.warningOptions.push_back("no-missing-top");
382 if (failed(
383 importVerilog(sourceMgr, context, ts, module.get(), &options)))
384 module = {};
385 return module;
386 });
387}
static Location convertLocation(MLIRContext *context, const slang::SourceManager &sourceManager, slang::SourceLocation loc)
Convert a slang SourceLocation to an MLIR Location.
static mlir::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)