CIRCT 23.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
19#include "mlir/Conversion/SCFToControlFlow/SCFToControlFlow.h"
20#include "mlir/IR/Diagnostics.h"
21#include "mlir/IR/Verifier.h"
22#include "mlir/Pass/PassManager.h"
23#include "mlir/Support/Timing.h"
24#include "mlir/Tools/mlir-translate/Translation.h"
25#include "mlir/Transforms/Passes.h"
26#include "llvm/ADT/Hashing.h"
27#include "llvm/Support/SourceMgr.h"
28
29#include "slang/diagnostics/DiagnosticClient.h"
30#include "slang/driver/Driver.h"
31#include "slang/parsing/Preprocessor.h"
32#include "slang/syntax/SyntaxPrinter.h"
33#include "slang/util/VersionInfo.h"
34
35using namespace mlir;
36using namespace circt;
37using namespace ImportVerilog;
38
39using llvm::SourceMgr;
40
42 std::string buffer;
43 llvm::raw_string_ostream os(buffer);
44 os << "slang version ";
45 os << slang::VersionInfo::getMajor() << ".";
46 os << slang::VersionInfo::getMinor() << ".";
47 os << slang::VersionInfo::getPatch() << "+";
48 os << slang::VersionInfo::getHash();
49 return buffer;
50}
51
52//===----------------------------------------------------------------------===//
53// Diagnostics
54//===----------------------------------------------------------------------===//
55
56/// Convert a slang `SourceLocation` to an MLIR `Location`.
57static Location convertLocation(MLIRContext *context,
58 const slang::SourceManager &sourceManager,
59 slang::SourceLocation loc) {
60 if (loc && loc.buffer() != slang::SourceLocation::NoLocation.buffer()) {
61 auto fileName = sourceManager.getFileName(loc);
62 auto line = sourceManager.getLineNumber(loc);
63 auto column = sourceManager.getColumnNumber(loc);
64 return FileLineColLoc::get(context, fileName, line, column);
65 }
66 return UnknownLoc::get(context);
67}
68
69Location Context::convertLocation(slang::SourceLocation loc) {
70 return ::convertLocation(getContext(), sourceManager, loc);
71}
72
73Location Context::convertLocation(slang::SourceRange range) {
74 return convertLocation(range.start());
75}
76
77namespace {
78/// A converter that can be plugged into a slang `DiagnosticEngine` as a client
79/// that will map slang diagnostics to their MLIR counterpart and emit them.
80class MlirDiagnosticClient : public slang::DiagnosticClient {
81public:
82 MlirDiagnosticClient(MLIRContext *context) : context(context) {}
83
84 void report(const slang::ReportedDiagnostic &diag) override {
85 // Generate the primary MLIR diagnostic.
86 auto &diagEngine = context->getDiagEngine();
87 auto mlirDiag = diagEngine.emit(convertLocation(diag.location),
88 getSeverity(diag.severity));
89 mlirDiag << diag.formattedMessage;
90
91 // Append the name of the option that can be used to control this
92 // diagnostic.
93 auto optionName = engine->getOptionName(diag.originalDiagnostic.code);
94 if (!optionName.empty())
95 mlirDiag << " [-W" << optionName << "]";
96
97 // Write out macro expansions, if we have any, in reverse order.
98 for (auto loc : std::views::reverse(diag.expansionLocs)) {
99 auto &note = mlirDiag.attachNote(
100 convertLocation(sourceManager->getFullyOriginalLoc(loc)));
101 auto macroName = sourceManager->getMacroName(loc);
102 if (macroName.empty())
103 note << "expanded from here";
104 else
105 note << "expanded from macro '" << macroName << "'";
106 }
107
108 // Write out the include stack.
109 slang::SmallVector<slang::SourceLocation> includeStack;
110 getIncludeStack(diag.location.buffer(), includeStack);
111 for (auto &loc : std::views::reverse(includeStack))
112 mlirDiag.attachNote(convertLocation(loc)) << "included from here";
113 }
114
115 /// Convert a slang `SourceLocation` to an MLIR `Location`.
116 Location convertLocation(slang::SourceLocation loc) const {
117 return ::convertLocation(context, *sourceManager, loc);
118 }
119
120 static DiagnosticSeverity getSeverity(slang::DiagnosticSeverity severity) {
121 switch (severity) {
122 case slang::DiagnosticSeverity::Fatal:
123 case slang::DiagnosticSeverity::Error:
124 return DiagnosticSeverity::Error;
125 case slang::DiagnosticSeverity::Warning:
126 return DiagnosticSeverity::Warning;
127 case slang::DiagnosticSeverity::Ignored:
128 case slang::DiagnosticSeverity::Note:
129 return DiagnosticSeverity::Remark;
130 }
131 llvm_unreachable("all slang diagnostic severities should be handled");
132 return DiagnosticSeverity::Error;
133 }
134
135private:
136 MLIRContext *context;
137};
138} // namespace
139
140// Allow for `slang::BufferID` to be used as hash map keys.
141namespace llvm {
142template <>
143struct DenseMapInfo<slang::BufferID> {
144 static slang::BufferID getEmptyKey() { return slang::BufferID(); }
145 static slang::BufferID getTombstoneKey() {
146 return slang::BufferID(UINT32_MAX - 1, ""sv);
147 // UINT32_MAX is already used by `BufferID::getPlaceholder`.
148 }
149 static unsigned getHashValue(slang::BufferID id) {
150 return llvm::hash_value(id.getId());
151 }
152 static bool isEqual(slang::BufferID a, slang::BufferID b) { return a == b; }
153};
154} // namespace llvm
155
156//===----------------------------------------------------------------------===//
157// Driver
158//===----------------------------------------------------------------------===//
159
160namespace {
161const static ImportVerilogOptions defaultOptions;
162
163struct ImportDriver {
164 ImportDriver(MLIRContext *mlirContext, TimingScope &ts,
165 const ImportVerilogOptions *options)
166 : mlirContext(mlirContext), ts(ts),
167 options(options ? *options : defaultOptions) {}
168
169 LogicalResult prepareDriver(SourceMgr &sourceMgr);
170 LogicalResult importVerilog(ModuleOp module);
171 LogicalResult preprocessVerilog(llvm::raw_ostream &os);
172
173 MLIRContext *mlirContext;
174 TimingScope &ts;
175 const ImportVerilogOptions &options;
176
177 // Use slang's driver which conveniently packages a lot of the things we
178 // need for compilation.
179 slang::driver::Driver driver;
180};
181} // namespace
182
183/// Populate the Slang driver with source files from the given `sourceMgr`, and
184/// configure driver options based on the `ImportVerilogOptions` passed to the
185/// `ImportDriver` constructor.
186LogicalResult ImportDriver::prepareDriver(SourceMgr &sourceMgr) {
187 // Use slang's driver which conveniently packages a lot of the things we
188 // need for compilation.
189 auto diagClient = std::make_shared<MlirDiagnosticClient>(mlirContext);
190 driver.diagEngine.addClient(diagClient);
191
192 for (const auto &value : options.commandFiles)
193 if (!driver.processCommandFiles(value, /*makeRelative=*/true,
194 /*separateUnit=*/true))
195 return failure();
196
197 // Populate the source manager with the source files.
198 // NOTE: This is a bit ugly since we're essentially copying the Verilog
199 // source text in memory. At a later stage we'll want to extend slang's
200 // SourceManager such that it can contain non-owned buffers. This will do
201 // for now.
202 DenseSet<StringRef> seenBuffers;
203 for (unsigned i = 0, e = sourceMgr.getNumBuffers(); i < e; ++i) {
204 const llvm::MemoryBuffer *mlirBuffer = sourceMgr.getMemoryBuffer(i + 1);
205 auto name = mlirBuffer->getBufferIdentifier();
206 if (!name.empty() && !seenBuffers.insert(name).second)
207 continue; // Slang doesn't like listing the same buffer twice
208 auto slangBuffer =
209 driver.sourceManager.assignText(name, mlirBuffer->getBuffer());
210 driver.sourceLoader.addBuffer(slangBuffer);
211 }
212
213 for (const auto &libDir : options.libDirs)
214 driver.sourceLoader.addSearchDirectories(libDir);
215
216 for (const auto &libExt : options.libExts)
217 driver.sourceLoader.addSearchExtension(libExt);
218
219 for (const auto &[i, f] : llvm::enumerate(options.libraryFiles)) {
220 // Include a space to avoid conflicts with explicitly-specified names.
221 auto libName = "library " + std::to_string(i);
222 driver.sourceLoader.addLibraryFiles(libName, f);
223 }
224
225 for (const auto &includeDir : options.includeDirs)
226 if (driver.sourceManager.addUserDirectories(includeDir))
227 return failure();
228
229 for (const auto &includeSystemDir : options.includeSystemDirs)
230 if (driver.sourceManager.addSystemDirectories(includeSystemDir))
231 return failure();
232
233 // Populate the driver options.
234 driver.options.excludeExts.insert(options.excludeExts.begin(),
235 options.excludeExts.end());
236 driver.options.ignoreDirectives = options.ignoreDirectives;
237
238 driver.options.maxIncludeDepth = options.maxIncludeDepth;
239 driver.options.defines = options.defines;
240 driver.options.undefines = options.undefines;
241 driver.options.librariesInheritMacros = options.librariesInheritMacros;
242
243 driver.options.timeScale = options.timeScale;
244 driver.options.compilationFlags.emplace(
245 slang::ast::CompilationFlags::AllowUseBeforeDeclare,
246 options.allowUseBeforeDeclare);
247 driver.options.compilationFlags.emplace(
248 slang::ast::CompilationFlags::IgnoreUnknownModules,
249 options.ignoreUnknownModules);
250 driver.options.compilationFlags.emplace(
251 slang::ast::CompilationFlags::LintMode,
253 driver.options.compilationFlags.emplace(
254 slang::ast::CompilationFlags::DisableInstanceCaching, false);
255 driver.options.topModules = options.topModules;
256 driver.options.paramOverrides = options.paramOverrides;
257
258 driver.options.errorLimit = options.errorLimit;
259 driver.options.warningOptions = options.warningOptions;
260
261 driver.options.singleUnit = options.singleUnit;
262
263 return success(driver.processOptions());
264}
265
266/// Parse and elaborate the prepared source files, and populate the given MLIR
267/// `module` with corresponding operations.
268LogicalResult ImportDriver::importVerilog(ModuleOp module) {
269 // Parse the input.
270 auto parseTimer = ts.nest("Verilog parser");
271 bool parseSuccess = driver.parseAllSources();
272 parseTimer.stop();
273
274 // Elaborate the input.
275 auto compileTimer = ts.nest("Verilog elaboration");
276 auto compilation = driver.createCompilation();
277 for (auto &diag : compilation->getAllDiagnostics())
278 driver.diagEngine.issue(diag);
279 if (!parseSuccess || driver.diagEngine.getNumErrors() > 0)
280 return failure();
281 compileTimer.stop();
282
283 // If we were only supposed to lint the input, return here. This leaves the
284 // module empty, but any Slang linting messages got reported as diagnostics.
285 if (options.mode == ImportVerilogOptions::Mode::OnlyLint)
286 return success();
287
288 // Traverse the parsed Verilog AST and map it to the equivalent CIRCT ops.
289 mlirContext
290 ->loadDialect<moore::MooreDialect, hw::HWDialect, cf::ControlFlowDialect,
291 func::FuncDialect, verif::VerifDialect, ltl::LTLDialect,
292 debug::DebugDialect>();
293 auto conversionTimer = ts.nest("Verilog to dialect mapping");
294 Context context(options, *compilation, module, driver.sourceManager);
295 if (failed(context.convertCompilation()))
296 return failure();
297 conversionTimer.stop();
298
299 // Run the verifier on the constructed module to ensure it is clean.
300 auto verifierTimer = ts.nest("Post-parse verification");
301 return verify(module);
302}
303
304/// Preprocess the prepared source files and print them to the given output
305/// stream.
306LogicalResult ImportDriver::preprocessVerilog(llvm::raw_ostream &os) {
307 auto parseTimer = ts.nest("Verilog preprocessing");
308
309 // Run the preprocessor to completion across all sources previously added with
310 // `pushSource`, report diagnostics, and print the output.
311 auto preprocessAndPrint = [&](slang::parsing::Preprocessor &preprocessor) {
312 slang::syntax::SyntaxPrinter output;
313 output.setIncludeComments(false);
314 while (true) {
315 slang::parsing::Token token = preprocessor.next();
316 output.print(token);
317 if (token.kind == slang::parsing::TokenKind::EndOfFile)
318 break;
319 }
320
321 for (auto &diag : preprocessor.getDiagnostics()) {
322 if (diag.isError()) {
323 driver.diagEngine.issue(diag);
324 return failure();
325 }
326 }
327 os << output.str();
328 return success();
329 };
330
331 // Depending on whether the single-unit option is set, either add all source
332 // files to a single preprocessor such that they share define macros and
333 // directives, or create a separate preprocessor for each, such that each
334 // source file is in its own compilation unit.
335 auto optionBag = driver.createOptionBag();
336 if (driver.options.singleUnit == true) {
337 slang::BumpAllocator alloc;
338 slang::Diagnostics diagnostics;
339 slang::parsing::Preprocessor preprocessor(driver.sourceManager, alloc,
340 diagnostics, optionBag);
341 // Sources have to be pushed in reverse, as they form a stack in the
342 // preprocessor. Last pushed source is processed first.
343 auto sources = driver.sourceLoader.loadSources();
344 for (auto &buffer : std::views::reverse(sources))
345 preprocessor.pushSource(buffer);
346 if (failed(preprocessAndPrint(preprocessor)))
347 return failure();
348 } else {
349 for (auto &buffer : driver.sourceLoader.loadSources()) {
350 slang::BumpAllocator alloc;
351 slang::Diagnostics diagnostics;
352 slang::parsing::Preprocessor preprocessor(driver.sourceManager, alloc,
353 diagnostics, optionBag);
354 preprocessor.pushSource(buffer);
355 if (failed(preprocessAndPrint(preprocessor)))
356 return failure();
357 }
358 }
359
360 return success();
361}
362
363//===----------------------------------------------------------------------===//
364// Entry Points
365//===----------------------------------------------------------------------===//
366
367/// Parse the specified Verilog inputs into the specified MLIR context.
368LogicalResult circt::importVerilog(SourceMgr &sourceMgr,
369 MLIRContext *mlirContext, TimingScope &ts,
370 ModuleOp module,
371 const ImportVerilogOptions *options) {
372 ImportDriver importDriver(mlirContext, ts, options);
373 if (failed(importDriver.prepareDriver(sourceMgr)))
374 return failure();
375 return importDriver.importVerilog(module);
376}
377
378/// Run the files in a source manager through Slang's Verilog preprocessor and
379/// emit the result to the given output stream.
380LogicalResult circt::preprocessVerilog(SourceMgr &sourceMgr,
381 MLIRContext *mlirContext,
382 TimingScope &ts, llvm::raw_ostream &os,
383 const ImportVerilogOptions *options) {
384 ImportDriver importDriver(mlirContext, ts, options);
385 if (failed(importDriver.prepareDriver(sourceMgr)))
386 return failure();
387 return importDriver.preprocessVerilog(os);
388}
389
390/// Entry point as an MLIR translation.
392 static TranslateToMLIRRegistration fromVerilog(
393 "import-verilog", "import Verilog or SystemVerilog",
394 [](llvm::SourceMgr &sourceMgr, MLIRContext *context) {
395 TimingScope ts;
397 ModuleOp::create(UnknownLoc::get(context)));
398 ImportVerilogOptions options;
399 options.debugInfo = true;
400 options.warningOptions.push_back("no-missing-top");
401 if (failed(
402 importVerilog(sourceMgr, context, ts, module.get(), &options)))
403 module = {};
404 return module;
405 });
406}
407
408//===----------------------------------------------------------------------===//
409// Pass Pipeline
410//===----------------------------------------------------------------------===//
411
412/// Optimize and simplify the Moore dialect IR.
413void circt::populateVerilogToMoorePipeline(OpPassManager &pm) {
414 {
415 // Perform an initial cleanup and preprocessing across all
416 // modules/functions.
417 auto &anyPM = pm.nestAny();
418 anyPM.addPass(mlir::createCSEPass());
419 anyPM.addPass(mlir::createCanonicalizerPass());
420 }
421
422 pm.addPass(moore::createVTablesPass());
423
424 // Remove unused symbols.
425 pm.addPass(mlir::createSymbolDCEPass());
426
427 {
428 // Perform module-specific transformations.
429 auto &modulePM = pm.nest<moore::SVModuleOp>();
430 modulePM.addPass(moore::createLowerConcatRefPass());
431 // TODO: Enable the following once it not longer interferes with @(...)
432 // event control checks. The introduced dummy variables make the event
433 // control observe a static local variable that never changes, instead of
434 // observing a module-wide signal.
435 // modulePM.addPass(moore::createSimplifyProceduresPass());
436 modulePM.addPass(mlir::createSROA());
437 }
438
439 {
440 // Perform a final cleanup across all modules/functions.
441 auto &anyPM = pm.nestAny();
442 anyPM.addPass(mlir::createMem2Reg());
443 anyPM.addPass(mlir::createCSEPass());
444 anyPM.addPass(mlir::createCanonicalizerPass());
445 }
446}
447
448/// Convert Moore dialect IR into core dialect IR
449void circt::populateMooreToCorePipeline(OpPassManager &pm) {
450 // Perform the conversion.
451 pm.addPass(createConvertMooreToCorePass());
452
453 {
454 // Conversion to the core dialects likely uncovers new canonicalization
455 // opportunities.
456 auto &anyPM = pm.nestAny();
457 anyPM.addPass(mlir::createCSEPass());
458 anyPM.addPass(mlir::createCanonicalizerPass());
459 }
460}
461
462/// Convert LLHD dialect IR into core dialect IR
464 OpPassManager &pm, const LlhdToCorePipelineOptions &options) {
465 // Inline function calls and lower SCF to CF.
466 pm.addNestedPass<hw::HWModuleOp>(llhd::createWrapProceduralOpsPass());
467 pm.addPass(mlir::createSCFToControlFlowPass());
468 pm.addPass(llhd::createInlineCallsPass());
469 pm.addPass(mlir::createSymbolDCEPass());
470
471 // Simplify processes, replace signals with process results, and detect
472 // registers.
473 auto &modulePM = pm.nest<hw::HWModuleOp>();
474 // See https://github.com/llvm/circt/issues/8804.
475 if (options.sroa) {
476 modulePM.addPass(mlir::createSROA());
477 }
478 modulePM.addPass(llhd::createMem2RegPass());
479 modulePM.addPass(llhd::createHoistSignalsPass());
480 modulePM.addPass(llhd::createDeseqPass());
481 modulePM.addPass(llhd::createLowerProcessesPass());
482 modulePM.addPass(mlir::createCSEPass());
483 modulePM.addPass(mlir::createCanonicalizerPass());
484
485 // Unroll loops and remove control flow.
486 modulePM.addPass(llhd::createUnrollLoopsPass());
487 modulePM.addPass(mlir::createCSEPass());
488 modulePM.addPass(mlir::createCanonicalizerPass());
489 modulePM.addPass(llhd::createRemoveControlFlowPass());
490 modulePM.addPass(mlir::createCSEPass());
491 modulePM.addPass(mlir::createCanonicalizerPass());
492
493 // Convert `arith.select` generated by some of the control flow canonicalizers
494 // to `comb.mux`.
495 modulePM.addPass(createMapArithToCombPass(true));
496
497 // Simplify module-level signals.
498 modulePM.addPass(llhd::createCombineDrivesPass());
499 modulePM.addPass(llhd::createSig2Reg());
500 modulePM.addPass(mlir::createCSEPass());
501 modulePM.addPass(mlir::createCanonicalizerPass());
502
503 // Map `seq.firreg` with array type and `hw.array_inject` self-feedback to
504 // `seq.firmem` ops.
505 if (options.detectMemories) {
506 modulePM.addPass(seq::createRegOfVecToMem());
507 modulePM.addPass(mlir::createCSEPass());
508 modulePM.addPass(mlir::createCanonicalizerPass());
509 }
510}
static std::unique_ptr< Context > context
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)
std::unique_ptr< mlir::Pass > createLowerConcatRefPass()
std::unique_ptr< mlir::Pass > createVTablesPass()
std::unique_ptr< mlir::Pass > createRegOfVecToMem()
The InstanceGraph op interface, see InstanceGraphInterface.td for more details.
std::unique_ptr< mlir::Pass > createMapArithToCombPass(bool enableBestEffortLowering=false)
void populateVerilogToMoorePipeline(mlir::OpPassManager &pm)
Optimize and simplify the Moore dialect IR.
void populateMooreToCorePipeline(mlir::OpPassManager &pm)
Convert Moore dialect IR into core dialect IR.
void populateLlhdToCorePipeline(mlir::OpPassManager &pm, const LlhdToCorePipelineOptions &options)
std::string getSlangVersion()
Return a human-readable string describing the slang frontend version linked into CIRCT.
std::unique_ptr< OperationPass< ModuleOp > > createConvertMooreToCorePass()
Create an Moore to Comb/HW/LLHD conversion pass.
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.
llvm::hash_code hash_value(const DenseSet< T > &set)
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.
Convert LLHD dialect IR into core dialect IR.
static bool isEqual(slang::BufferID a, slang::BufferID b)
static slang::BufferID getTombstoneKey()
static unsigned getHashValue(slang::BufferID id)