CIRCT  20.0.0git
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 
13 #include "ImportVerilogInternals.h"
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 
28 using namespace mlir;
29 using namespace circt;
30 using namespace ImportVerilog;
31 
32 using llvm::SourceMgr;
33 
34 std::string circt::getSlangVersion() {
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`.
50 static Location
51 convertLocation(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 
63 Location Context::convertLocation(slang::SourceLocation loc) {
64  return ::convertLocation(getContext(), sourceManager, bufferFilePaths, loc);
65 }
66 
67 Location Context::convertLocation(slang::SourceRange range) {
68  return convertLocation(range.start());
69 }
70 
71 namespace {
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.
74 class MlirDiagnosticClient : public slang::DiagnosticClient {
75 public:
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 
127 private:
128  MLIRContext *context;
130 };
131 } // namespace
132 
133 // Allow for `slang::BufferID` to be used as hash map keys.
134 namespace llvm {
135 template <>
136 struct 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 
153 namespace {
154 const static ImportVerilogOptions defaultOptions;
155 
156 struct 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.
189 LogicalResult 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 =
227  options.mode == ImportVerilogOptions::Mode::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.
246 LogicalResult 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.
284 LogicalResult 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.
346 static LogicalResult
347 catchExceptions(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.
356 LogicalResult 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.
370 LogicalResult 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;
388  OwningOpRef<ModuleOp> module(
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 llvm::hash_code hash_value(const ElaboratorValue &val)
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.
static LogicalResult verify(Value clock, bool eventExists, mlir::Location loc)
Definition: SVOps.cpp:2467
Direction get(bool isOutput)
Returns an output direction if isOutput is true, otherwise returns an input direction.
Definition: CalyxOps.cpp:55
The InstanceGraph op interface, see InstanceGraphInterface.td for more details.
Definition: DebugAnalysis.h:21
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.
Definition: ImportVerilog.h:37
bool debugInfo
Generate debug information in the form of debug dialect ops in the IR.
Definition: ImportVerilog.h:50
A helper class to facilitate the conversion from a Slang AST to MLIR operations.
static bool isEqual(slang::BufferID a, slang::BufferID b)
static slang::BufferID getEmptyKey()
static slang::BufferID getTombstoneKey()
static unsigned getHashValue(slang::BufferID id)