CIRCT  19.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  // Traverse the parsed Verilog AST and map it to the equivalent CIRCT ops.
262  mlirContext->loadDialect<moore::MooreDialect, hw::HWDialect, scf::SCFDialect,
263  func::FuncDialect>();
264  auto conversionTimer = ts.nest("Verilog to dialect mapping");
265  Context context(*compilation, module, driver.sourceManager, bufferFilePaths);
266  if (failed(context.convertCompilation()))
267  return failure();
268  conversionTimer.stop();
269 
270  // Run the verifier on the constructed module to ensure it is clean.
271  auto verifierTimer = ts.nest("Post-parse verification");
272  return verify(module);
273 }
274 
275 /// Preprocess the prepared source files and print them to the given output
276 /// stream.
277 LogicalResult ImportDriver::preprocessVerilog(llvm::raw_ostream &os) {
278  auto parseTimer = ts.nest("Verilog preprocessing");
279 
280  // Run the preprocessor to completion across all sources previously added with
281  // `pushSource`, report diagnostics, and print the output.
282  auto preprocessAndPrint = [&](slang::parsing::Preprocessor &preprocessor) {
283  slang::syntax::SyntaxPrinter output;
284  output.setIncludeComments(false);
285  while (true) {
286  slang::parsing::Token token = preprocessor.next();
287  output.print(token);
288  if (token.kind == slang::parsing::TokenKind::EndOfFile)
289  break;
290  }
291 
292  for (auto &diag : preprocessor.getDiagnostics()) {
293  if (diag.isError()) {
294  driver.diagEngine.issue(diag);
295  return failure();
296  }
297  }
298  os << output.str();
299  return success();
300  };
301 
302  // Depending on whether the single-unit option is set, either add all source
303  // files to a single preprocessor such that they share define macros and
304  // directives, or create a separate preprocessor for each, such that each
305  // source file is in its own compilation unit.
306  auto optionBag = driver.createOptionBag();
307  if (driver.options.singleUnit == true) {
308  slang::BumpAllocator alloc;
309  slang::Diagnostics diagnostics;
310  slang::parsing::Preprocessor preprocessor(driver.sourceManager, alloc,
311  diagnostics, optionBag);
312  // Sources have to be pushed in reverse, as they form a stack in the
313  // preprocessor. Last pushed source is processed first.
314  for (auto &buffer : slang::make_reverse_range(driver.buffers))
315  preprocessor.pushSource(buffer);
316  if (failed(preprocessAndPrint(preprocessor)))
317  return failure();
318  } else {
319  for (auto &buffer : driver.buffers) {
320  slang::BumpAllocator alloc;
321  slang::Diagnostics diagnostics;
322  slang::parsing::Preprocessor preprocessor(driver.sourceManager, alloc,
323  diagnostics, optionBag);
324  preprocessor.pushSource(buffer);
325  if (failed(preprocessAndPrint(preprocessor)))
326  return failure();
327  }
328  }
329 
330  return success();
331 }
332 
333 //===----------------------------------------------------------------------===//
334 // Entry Points
335 //===----------------------------------------------------------------------===//
336 
337 /// Execute a callback and report any thrown exceptions as "internal slang
338 /// error" MLIR diagnostics.
339 static LogicalResult
340 catchExceptions(llvm::function_ref<LogicalResult()> callback) {
341  try {
342  return callback();
343  } catch (const std::exception &e) {
344  return emitError(UnknownLoc(), "internal slang error: ") << e.what();
345  }
346 }
347 
348 /// Parse the specified Verilog inputs into the specified MLIR context.
349 LogicalResult circt::importVerilog(SourceMgr &sourceMgr,
350  MLIRContext *mlirContext, TimingScope &ts,
351  ModuleOp module,
352  const ImportVerilogOptions *options) {
353  return catchExceptions([&] {
354  ImportDriver importDriver(mlirContext, ts, options);
355  if (failed(importDriver.prepareDriver(sourceMgr)))
356  return failure();
357  return importDriver.importVerilog(module);
358  });
359 }
360 
361 /// Run the files in a source manager through Slang's Verilog preprocessor and
362 /// emit the result to the given output stream.
363 LogicalResult circt::preprocessVerilog(SourceMgr &sourceMgr,
364  MLIRContext *mlirContext,
365  TimingScope &ts, llvm::raw_ostream &os,
366  const ImportVerilogOptions *options) {
367  return catchExceptions([&] {
368  ImportDriver importDriver(mlirContext, ts, options);
369  if (failed(importDriver.prepareDriver(sourceMgr)))
370  return failure();
371  return importDriver.preprocessVerilog(os);
372  });
373 }
374 
375 /// Entry point as an MLIR translation.
377  static TranslateToMLIRRegistration fromVerilog(
378  "import-verilog", "import Verilog or SystemVerilog",
379  [](llvm::SourceMgr &sourceMgr, MLIRContext *context) {
380  TimingScope ts;
381  OwningOpRef<ModuleOp> module(
382  ModuleOp::create(UnknownLoc::get(context)));
383  if (failed(importVerilog(sourceMgr, context, ts, module.get())))
384  module = {};
385  return module;
386  });
387 }
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:2443
Direction get(bool isOutput)
Returns an output direction if isOutput is true, otherwise returns an input direction.
Definition: CalyxOps.cpp:54
llvm::hash_code hash_value(const BundledChannel channel)
Definition: ESITypes.h:48
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
A helper class to facilitate the conversion from a Slang AST to MLIR operations.
Location convertLocation(slang::SourceRange range)
Convert a slang SourceRange into an MLIR Location.
static bool isEqual(slang::BufferID a, slang::BufferID b)
static slang::BufferID getEmptyKey()
static slang::BufferID getTombstoneKey()
static unsigned getHashValue(slang::BufferID id)