CIRCT  19.0.0git
EmitHGLDD.cpp
Go to the documentation of this file.
1 //===- EmitHGLDD.cpp - HGLDD debug info emission --------------------------===//
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 
12 #include "circt/Dialect/HW/HWOps.h"
14 #include "circt/Dialect/SV/SVOps.h"
16 #include "circt/Target/DebugInfo.h"
17 #include "mlir/IR/Threading.h"
18 #include "mlir/Support/FileUtilities.h"
19 #include "mlir/Support/IndentedOstream.h"
20 #include "llvm/Support/Debug.h"
21 #include "llvm/Support/FileSystem.h"
22 #include "llvm/Support/JSON.h"
23 #include "llvm/Support/Path.h"
24 #include "llvm/Support/ToolOutputFile.h"
25 
26 #define DEBUG_TYPE "di"
27 
28 using namespace mlir;
29 using namespace circt;
30 using namespace debug;
31 
32 using llvm::MapVector;
33 using llvm::SmallMapVector;
34 
35 using JValue = llvm::json::Value;
36 using JArray = llvm::json::Array;
38 using JOStream = llvm::json::OStream;
39 
40 /// Walk the given `loc` and collect file-line-column locations that we want to
41 /// report as source ("HGL") locations or as emitted Verilog ("HDL") locations.
42 ///
43 /// This function treats locations inside a `NameLoc` called "emitted" or a
44 /// `FusedLoc` with the metadata attribute string "verilogLocations" as emitted
45 /// Verilog locations. All other locations are considered to be source
46 /// locations.
47 ///
48 /// The `level` parameter is used to track into how many "emitted" or
49 /// "verilogLocations" we have already descended. For every one of those we look
50 /// through the level gets decreased by one. File-line-column locations are only
51 /// collected at level 0. We don't descend into "emitted" or "verilogLocations"
52 /// once we've reached level 0. This effectively makes the `level` parameter
53 /// decide behind how many layers of "emitted" or "verilogLocations" we want to
54 /// collect file-line-column locations. Setting this to 0 effectively collects
55 /// source locations, i.e., everything not marked as emitted. Setting this to 1
56 /// effectively collects emitted locations, i.e., nothing that isn't behind
57 /// exactly one layer of "emitted" or "verilogLocations".
58 static void findLocations(Location loc, unsigned level,
59  SmallVectorImpl<FileLineColLoc> &locs) {
60  if (auto nameLoc = dyn_cast<NameLoc>(loc)) {
61  if (nameLoc.getName() == "emitted")
62  if (level-- == 0)
63  return;
64  findLocations(nameLoc.getChildLoc(), level, locs);
65  } else if (auto fusedLoc = dyn_cast<FusedLoc>(loc)) {
66  auto strAttr = dyn_cast_or_null<StringAttr>(fusedLoc.getMetadata());
67  if (strAttr && strAttr.getValue() == "verilogLocations")
68  if (level-- == 0)
69  return;
70  for (auto innerLoc : fusedLoc.getLocations())
71  findLocations(innerLoc, level, locs);
72  } else if (auto fileLoc = dyn_cast<FileLineColLoc>(loc)) {
73  if (level == 0)
74  locs.push_back(fileLoc);
75  }
76 }
77 
78 /// Find the best location to report as source location ("HGL", emitted = false)
79 /// or as emitted location ("HDL", emitted = true). Returns any non-FIR file it
80 /// finds, and only falls back to FIR files if nothing else is found.
81 static FileLineColLoc findBestLocation(Location loc, bool emitted,
82  bool fileMustExist) {
83  SmallVector<FileLineColLoc> locs;
84  findLocations(loc, emitted ? 1 : 0, locs);
85  if (fileMustExist) {
86  unsigned tail = 0;
87  for (unsigned head = 0, end = locs.size(); head != end; ++head)
88  if (llvm::sys::fs::exists(locs[head].getFilename().getValue()))
89  locs[tail++] = locs[head];
90  locs.resize(tail);
91  }
92  for (auto loc : locs)
93  if (!loc.getFilename().getValue().ends_with(".fir"))
94  return loc;
95  for (auto loc : locs)
96  if (loc.getFilename().getValue().ends_with(".fir"))
97  return loc;
98  return {};
99 }
100 
101 // Allow `json::Value`s to be used as map keys for the purpose of struct
102 // definition uniquification. This abuses the `null` and `[null]` JSON values as
103 // markers, and uses a very inefficient hashing of the value's JSON string.
104 namespace llvm {
105 template <>
106 struct DenseMapInfo<JValue> {
107  static JValue getEmptyKey() { return nullptr; }
108  static JValue getTombstoneKey() { return JArray({nullptr}); }
109  static unsigned getHashValue(const JValue &x) {
110  SmallString<128> buffer;
111  llvm::raw_svector_ostream(buffer) << x;
112  return hash_value(buffer);
113  }
114  static bool isEqual(const JValue &a, const JValue &b) { return a == b; }
115 };
116 } // namespace llvm
117 
118 /// Make the given `path` relative to the `relativeTo` path and store the result
119 /// in `relativePath`. Returns whether the conversion was successful. Fails if
120 /// the `relativeTo` path has a longer prefix of `../` than `path`, or if it
121 /// contains any non-prefix `../` components. Does not clear `relativePath`
122 /// before appending to it.
123 static bool makePathRelative(StringRef path, StringRef relativeTo,
124  SmallVectorImpl<char> &relativePath) {
125  using namespace llvm::sys;
126  auto sourceIt = path::begin(path);
127  auto outputIt = path::begin(relativeTo);
128  auto sourceEnd = path::end(path);
129  auto outputEnd = path::end(relativeTo);
130 
131  // Strip common prefix:
132  // - (), () -> (), ()
133  // - (a/b/c/d), (a/b/e/f) -> (c/d), (e/f)
134  // - (a/b), (a/b/c/d) -> (), (c/d)
135  // - (../a/b), (../a/c) -> (b), (c)
136  while (outputIt != outputEnd && sourceIt != sourceEnd &&
137  *outputIt == *sourceIt) {
138  ++outputIt;
139  ++sourceIt;
140  }
141 
142  // For every component in the output path insert a `../` into the source
143  // path. Abort if the output path contains a `../`, because we don't
144  // know where that climbs out to. Consider the changes to the following
145  // output-source pairs as an example:
146  //
147  // - (a/b), (c/d) -> (), (../../c/d)
148  // - (), (a/b) -> (), (a/b)
149  // - (../a), (c/d) -> (../a), (c/d)
150  // - (a/../b), (c/d) -> (../b), (../c/d)
151  for (; outputIt != outputEnd && *outputIt != ".."; ++outputIt)
152  path::append(relativePath, "..");
153  for (; sourceIt != sourceEnd; ++sourceIt)
154  path::append(relativePath, *sourceIt);
155 
156  // If there are no more remaining components in the output path, we were
157  // successfully able to translate them into `..` in the source path.
158  // Otherwise the `relativeTo` path contained `../` components that we could
159  // not handle.
160  return outputIt == outputEnd;
161 }
162 
163 /// Legalize the given `name` such that it only consists of valid identifier
164 /// characters in Verilog and does not collide with any Verilog keyword, and
165 /// uniquify the resulting name such that it does not collide with any of the
166 /// names stored in the `nextGeneratedNameIDs` map.
167 ///
168 /// The HGLDD output is likely to be ingested by tools that have been developed
169 /// to debug Verilog code. These have the limitation of only supporting valid
170 /// Verilog identifiers for signals and other names. HGLDD therefore requires
171 /// these names to be valid Verilog identifiers.
172 static StringRef legalizeName(StringRef name,
173  llvm::StringMap<size_t> &nextGeneratedNameIDs) {
174  return sv::legalizeName(name, nextGeneratedNameIDs, true);
175 }
176 
177 //===----------------------------------------------------------------------===//
178 // HGLDD File Emission
179 //===----------------------------------------------------------------------===//
180 
181 namespace {
182 
183 /// Contextual information for HGLDD emission shared across multiple HGLDD
184 /// files. This struct keeps the DI analysis, symbol caches, and namespaces for
185 /// name uniquification.
186 struct GlobalState {
187  /// The root operation.
188  Operation *op;
189  /// The emission options.
190  const EmitHGLDDOptions &options;
191  /// The debug info analysis constructed for the root operation.
192  DebugInfo di;
193  /// A symbol cache with all top-level symbols in the root operation.
194  hw::HWSymbolCache symbolCache;
195  /// A uniquified name for each emitted DI module.
197  /// A namespace used to deduplicate the names of HGLDD "objects" during
198  /// emission. This includes modules and struct type declarations.
199  llvm::StringMap<size_t> objectNamespace;
200 
201  GlobalState(Operation *op, const EmitHGLDDOptions &options)
202  : op(op), options(options), di(op) {
203  symbolCache.addDefinitions(op);
204  symbolCache.freeze();
205  }
206 };
207 
208 /// An emitted type.
209 struct EmittedType {
210  StringRef name;
211  SmallVector<int64_t, 1> packedDims;
212  SmallVector<int64_t, 1> unpackedDims;
213 
214  EmittedType() = default;
215  EmittedType(StringRef name) : name(name) {}
216  EmittedType(Type type) {
217  type = hw::getCanonicalType(type);
218  if (auto inoutType = dyn_cast<hw::InOutType>(type))
219  type = hw::getCanonicalType(inoutType.getElementType());
220  if (hw::isHWIntegerType(type)) {
221  name = "logic";
222  addPackedDim(hw::getBitWidth(type));
223  }
224  }
225 
226  void addPackedDim(int64_t dim) { packedDims.push_back(dim); }
227  void addUnpackedDim(int64_t dim) { unpackedDims.push_back(dim); }
228 
229  operator bool() const { return !name.empty(); }
230 
231  static JArray emitDims(ArrayRef<int64_t> dims, bool skipFirstLen1Dim) {
232  JArray json;
233  if (skipFirstLen1Dim && !dims.empty() && dims[0] == 1)
234  dims = dims.drop_front();
235  for (auto dim : llvm::reverse(dims)) {
236  json.push_back(dim - 1);
237  json.push_back(0);
238  }
239  return json;
240  }
241  JArray emitPackedDims() const { return emitDims(packedDims, true); }
242  JArray emitUnpackedDims() const { return emitDims(unpackedDims, false); }
243 };
244 
245 /// An emitted expression and its type.
246 struct EmittedExpr {
247  JValue expr = nullptr;
248  EmittedType type;
249  operator bool() const { return expr != nullptr && type; }
250 };
251 
252 static llvm::raw_ostream &operator<<(llvm::raw_ostream &os,
253  const EmittedType &type) {
254  if (!type)
255  return os << "<null>";
256  os << type.name;
257  for (auto dim : type.packedDims)
258  os << '[' << dim << ']';
259  if (!type.unpackedDims.empty()) {
260  os << '$';
261  for (auto dim : type.unpackedDims)
262  os << '[' << dim << ']';
263  }
264  return os;
265 }
266 
267 static llvm::raw_ostream &operator<<(llvm::raw_ostream &os,
268  const EmittedExpr &expr) {
269  if (!expr)
270  return os << "<null>";
271  return os << expr.expr << " : " << expr.type;
272 }
273 
274 /// Contextual information for a single HGLDD file to be emitted.
275 struct FileEmitter {
276  GlobalState &state;
277  StringAttr hdlFile;
278  FileEmitter(GlobalState &state, StringAttr hdlFile)
279  : state(state), hdlFile(hdlFile) {}
280 
281  /// The DI modules to be emitted into this HGLDD file.
282  SmallVector<DIModule *> modules;
283  /// The name of the output HGLDD file.
284  SmallString<64> outputFileName;
285 
286  llvm::StringMap<size_t> moduleNamespace;
287  SmallMapVector<StringAttr, std::pair<StringAttr, unsigned>, 8> sourceFiles;
288  SmallMapVector<JValue, StringRef, 8> structDefs;
289  SmallString<128> structNameHint;
290 
291  void emit(llvm::raw_ostream &os);
292  void emit(JOStream &json);
293  JValue emitLoc(FileLineColLoc loc, FileLineColLoc endLoc, bool emitted);
294  void emitModule(JOStream &json, DIModule *module);
295  void emitModuleBody(JOStream &json, DIModule *module);
296  void emitInstance(JOStream &json, DIInstance *instance);
297  void emitVariable(JOStream &json, DIVariable *variable);
298  EmittedExpr emitExpression(Value value);
299 
300  unsigned getSourceFile(StringAttr sourceFile, bool emitted);
301 
302  FileLineColLoc findBestLocation(Location loc, bool emitted) {
303  return ::findBestLocation(loc, emitted, state.options.onlyExistingFileLocs);
304  }
305 
306  /// Find the best location and, if one is found, emit it under the given
307  /// `fieldName`.
308  void findAndEmitLoc(JOStream &json, StringRef fieldName, Location loc,
309  bool emitted) {
310  if (auto fileLoc = findBestLocation(loc, emitted))
311  json.attribute(fieldName, emitLoc(fileLoc, {}, emitted));
312  }
313 
314  /// Find the best location and, if one is found, emit it under the given
315  /// `fieldName`. If none is found, guess a location by looking at nested
316  /// operations.
317  void findAndEmitLocOrGuess(JOStream &json, StringRef fieldName, Operation *op,
318  bool emitted) {
319  if (auto fileLoc = findBestLocation(op->getLoc(), emitted)) {
320  json.attribute(fieldName, emitLoc(fileLoc, {}, emitted));
321  return;
322  }
323 
324  // Otherwise do a majority vote on the file name to report as location. Walk
325  // the operation, collect all locations, and group them by file name.
326  SmallMapVector<StringAttr, std::pair<SmallVector<FileLineColLoc>, unsigned>,
327  4>
328  locsByFile;
329  op->walk([&](Operation *subop) {
330  // Consider operations.
331  if (auto fileLoc = findBestLocation(subop->getLoc(), emitted))
332  locsByFile[fileLoc.getFilename()].first.push_back(fileLoc);
333 
334  // Consider block arguments.
335  for (auto &region : subop->getRegions())
336  for (auto &block : region)
337  for (auto arg : block.getArguments())
338  if (auto fileLoc = findBestLocation(arg.getLoc(), emitted))
339  locsByFile[fileLoc.getFilename()].first.push_back(fileLoc);
340  });
341 
342  // Give immediate block arguments a larger weight.
343  for (auto &region : op->getRegions())
344  for (auto &block : region)
345  for (auto arg : block.getArguments())
346  if (auto fileLoc = findBestLocation(arg.getLoc(), emitted))
347  locsByFile[fileLoc.getFilename()].second += 10;
348 
349  if (locsByFile.empty())
350  return;
351 
352  // Pick the highest-scoring file and create a location from it.
353  llvm::sort(locsByFile, [](auto &a, auto &b) {
354  return (a.second.first.size() + a.second.second) >
355  (b.second.first.size() + a.second.second);
356  });
357 
358  auto &locs = locsByFile.front().second.first;
359  llvm::sort(locs, [](auto &a, auto &b) {
360  if (a.getLine() < b.getLine())
361  return true;
362  if (a.getLine() > b.getLine())
363  return false;
364  if (a.getColumn() < b.getColumn())
365  return true;
366  return false;
367  });
368 
369  json.attribute(fieldName, emitLoc(locs.front(), locs.back(), emitted));
370  }
371 
372  /// Find the best locations to report for HGL and HDL and set them as fields
373  /// on the `into` JSON object.
374  void findAndSetLocs(JObject &into, Location loc) {
375  if (auto fileLoc = findBestLocation(loc, false))
376  into["hgl_loc"] = emitLoc(fileLoc, {}, false);
377  if (auto fileLoc = findBestLocation(loc, true))
378  into["hdl_loc"] = emitLoc(fileLoc, {}, true);
379  }
380 
381  /// Return the legalized and uniquified name of a DI module. Asserts that the
382  /// module has such a name.
383  StringRef getModuleName(DIModule *module) {
384  auto name = state.moduleNames.lookup(module);
385  assert(!name.empty() &&
386  "moduleNames should contain a name for every module");
387  return name;
388  }
389 
390  StringRef legalizeInModuleNamespace(StringRef name) {
391  return legalizeName(name, moduleNamespace);
392  }
393 };
394 
395 } // namespace
396 
397 /// Get a numeric index for the given `sourceFile`. Populates `sourceFiles`
398 /// with a unique ID assignment for each source file.
399 unsigned FileEmitter::getSourceFile(StringAttr sourceFile, bool emitted) {
400  using namespace llvm::sys;
401 
402  // Check if we have already allocated an ID for this source file. If we
403  // have, return it. Otherwise, assign a new ID and normalize the path
404  // according to HGLDD requirements.
405  auto &slot = sourceFiles[sourceFile];
406  if (slot.first)
407  return slot.second;
408  slot.second = sourceFiles.size();
409 
410  // If the source file is an absolute path, simply use that unchanged.
411  if (path::is_absolute(sourceFile.getValue())) {
412  slot.first = sourceFile;
413  return slot.second;
414  }
415 
416  // If specified, apply the output file prefix if this is an output file
417  // (`emitted` is true), or the source file prefix if this is a source file
418  // (`emitted` is false).
419  StringRef filePrefix =
420  emitted ? state.options.outputFilePrefix : state.options.sourceFilePrefix;
421  if (!filePrefix.empty()) {
422  SmallString<64> buffer = filePrefix;
423  path::append(buffer, sourceFile.getValue());
424  slot.first = StringAttr::get(sourceFile.getContext(), buffer);
425  return slot.second;
426  }
427 
428  // Otherwise make the path relative to the HGLDD output file.
429 
430  // Remove any `./` and `../` inside the path. This has also been applied
431  // to the `outputFileName`. As a result, both paths start with zero or
432  // more `../`, followed by the rest of the path without any `./` or `../`.
433  SmallString<64> sourcePath = sourceFile.getValue();
434  path::remove_dots(sourcePath, true);
435 
436  // If the output file is also relative, try to determine the relative path
437  // between them directly.
438  StringRef relativeToDir = path::parent_path(outputFileName);
439  if (!path::is_absolute(outputFileName)) {
440  SmallString<64> buffer;
441  if (makePathRelative(sourcePath, relativeToDir, buffer)) {
442  slot.first = StringAttr::get(sourceFile.getContext(), buffer);
443  return slot.second;
444  }
445  }
446 
447  // If the above failed, try to make the output and source paths absolute and
448  // retry computing a relative path. Only do this if conversion to absolute
449  // paths is successful for both paths, and if the resulting paths have at
450  // least the first path component in common. This prevents computing a
451  // relative path between `/home/foo/bar` and `/tmp/baz/noob` as
452  // `../../../tmp/baz/noob`.
453  SmallString<64> outputPath = relativeToDir;
454  fs::make_absolute(sourcePath);
455  fs::make_absolute(outputPath);
456  if (path::is_absolute(sourcePath) && path::is_absolute(outputPath)) {
457  auto firstSourceComponent = *path::begin(path::relative_path(sourcePath));
458  auto firstOutputComponent = *path::begin(path::relative_path(outputPath));
459  if (firstSourceComponent == firstOutputComponent) {
460  SmallString<64> buffer;
461  if (makePathRelative(sourcePath, outputPath, buffer)) {
462  slot.first = StringAttr::get(sourceFile.getContext(), buffer);
463  return slot.second;
464  }
465  }
466  }
467 
468  // Otherwise simply use the absolute source file path.
469  slot.first = StringAttr::get(sourceFile.getContext(), sourcePath);
470  return slot.second;
471 }
472 
473 void FileEmitter::emit(llvm::raw_ostream &os) {
474  JOStream json(os, 2);
475  emit(json);
476  os << "\n";
477 }
478 
480  // The "HGLDD" header field needs to be the first in the JSON file (which
481  // violates the JSON spec, but what can you do). But we only know after module
482  // emission what the contents of the header will be.
483  SmallVector<std::string, 16> rawObjects;
484  for (auto *module : modules) {
485  llvm::raw_string_ostream objectOS(rawObjects.emplace_back());
486  JOStream objectJson(objectOS, 2);
487  objectJson.arrayBegin(); // dummy for indentation
488  objectJson.arrayBegin(); // dummy for indentation
489  emitModule(objectJson, module);
490  objectJson.arrayEnd(); // dummy for indentation
491  objectJson.arrayEnd(); // dummy for indentation
492  }
493 
494  std::optional<unsigned> hdlFileIndex;
495  if (hdlFile)
496  hdlFileIndex = getSourceFile(hdlFile, true);
497 
498  json.objectBegin();
499  json.attributeObject("HGLDD", [&] {
500  json.attribute("version", "1.0");
501  json.attributeArray("file_info", [&] {
502  for (auto [key, fileAndId] : sourceFiles)
503  json.value(fileAndId.first.getValue());
504  });
505  if (hdlFileIndex)
506  json.attribute("hdl_file_index", *hdlFileIndex);
507  });
508  json.attributeArray("objects", [&] {
509  for (auto &[structDef, name] : structDefs)
510  json.value(structDef);
511  for (auto &rawObject : rawObjects) {
512  // The "rawObject" is nested within two dummy arrays (`[[<stuff>]]`) to
513  // make the indentation of the actual object JSON inside line up with the
514  // current scope (`{"objects":[<stuff>]}`). This is a bit of a hack, but
515  // allows us to use the JSON `OStream` API when constructing the modules
516  // above, creating a simple string buffer, instead of building up a
517  // potentially huge in-memory hierarchy of JSON objects for every module
518  // first. To remove the two dummy arrays, we drop the `[` and `]` at the
519  // front and back twice, and trim the remaining whitespace after each
520  // (since the actual string looks a lot more like
521  // `[\n [\n <stuff>\n ]\n]`).
522  json.rawValue(StringRef(rawObject)
523  .drop_front()
524  .drop_back()
525  .trim()
526  .drop_front()
527  .drop_back()
528  .trim());
529  }
530  });
531  json.objectEnd();
532 }
533 
534 JValue FileEmitter::emitLoc(FileLineColLoc loc, FileLineColLoc endLoc,
535  bool emitted) {
536  JObject obj;
537  obj["file"] = getSourceFile(loc.getFilename(), emitted);
538  if (loc.getLine()) {
539  obj["begin_line"] = loc.getLine();
540  obj["end_line"] = loc.getLine();
541  }
542  if (loc.getColumn()) {
543  obj["begin_column"] = loc.getColumn();
544  obj["end_column"] = loc.getColumn();
545  }
546  if (endLoc) {
547  if (endLoc.getLine())
548  obj["end_line"] = endLoc.getLine();
549  if (endLoc.getColumn())
550  obj["end_column"] = endLoc.getColumn();
551  }
552  return obj;
553 }
554 
555 StringAttr getVerilogModuleName(DIModule &module) {
556  if (auto *op = module.op)
557  if (auto attr = op->getAttrOfType<StringAttr>("verilogName"))
558  return attr;
559  return module.name;
560 }
561 
563  if (auto *op = inst.op)
564  if (auto attr = op->getAttrOfType<StringAttr>("hw.verilogName"))
565  return attr;
566  return inst.name;
567 }
568 
569 /// Emit the debug info for a `DIModule`.
570 void FileEmitter::emitModule(JOStream &json, DIModule *module) {
571  moduleNamespace = state.objectNamespace;
572  structNameHint = module->name.getValue();
573  json.objectBegin();
574  json.attribute("kind", "module");
575  json.attribute("obj_name", getModuleName(module)); // HGL
576  json.attribute("module_name",
577  getVerilogModuleName(*module).getValue()); // HDL
578  if (module->isExtern)
579  json.attribute("isExtModule", 1);
580  if (auto *op = module->op) {
581  findAndEmitLocOrGuess(json, "hgl_loc", op, false);
582  findAndEmitLoc(json, "hdl_loc", op->getLoc(), true);
583  }
584  emitModuleBody(json, module);
585  json.objectEnd();
586 }
587 
588 /// Emit the debug info for a `DIModule` body.
589 void FileEmitter::emitModuleBody(JOStream &json, DIModule *module) {
590  json.attributeArray("port_vars", [&] {
591  for (auto *var : module->variables)
592  emitVariable(json, var);
593  });
594  json.attributeArray("children", [&] {
595  for (auto *instance : module->instances)
596  emitInstance(json, instance);
597  });
598 }
599 
600 /// Emit the debug info for a `DIInstance`.
601 void FileEmitter::emitInstance(JOStream &json, DIInstance *instance) {
602  json.objectBegin();
603 
604  // Emit the instance and module name.
605  auto instanceName = legalizeInModuleNamespace(instance->name.getValue());
606  json.attribute("name", instanceName);
607  if (!instance->module->isInline) {
608  auto verilogName = getVerilogInstanceName(*instance);
609  if (verilogName != instanceName)
610  json.attribute("hdl_obj_name", verilogName.getValue());
611 
612  json.attribute("obj_name",
613  getModuleName(instance->module)); // HGL
614  json.attribute("module_name",
615  getVerilogModuleName(*instance->module).getValue()); // HDL
616  }
617 
618  if (auto *op = instance->op) {
619  findAndEmitLoc(json, "hgl_loc", op->getLoc(), false);
620  findAndEmitLoc(json, "hdl_loc", op->getLoc(), true);
621  }
622 
623  // Emit the module body inline if this is an inline scope.
624  if (instance->module->isInline) {
625  auto structNameHintLen = structNameHint.size();
626  if (!instance->module->name.empty()) {
627  structNameHint += '_';
628  structNameHint += instance->module->name.getValue();
629  } else if (!instance->name.empty()) {
630  structNameHint += '_';
631  structNameHint += instanceName;
632  }
633  emitModuleBody(json, instance->module);
634  structNameHint.resize(structNameHintLen);
635  }
636 
637  json.objectEnd();
638 }
639 
640 /// Emit the debug info for a `DIVariable`.
641 void FileEmitter::emitVariable(JOStream &json, DIVariable *variable) {
642  json.objectBegin();
643  auto variableName = legalizeInModuleNamespace(variable->name.getValue());
644  json.attribute("var_name", variableName);
645  findAndEmitLoc(json, "hgl_loc", variable->loc, false);
646  findAndEmitLoc(json, "hdl_loc", variable->loc, true);
647 
648  EmittedExpr emitted;
649  if (auto value = variable->value) {
650  auto structNameHintLen = structNameHint.size();
651  structNameHint += '_';
652  structNameHint += variableName;
653  emitted = emitExpression(value);
654  structNameHint.resize(structNameHintLen);
655  }
656 
657  LLVM_DEBUG(llvm::dbgs() << "- " << variable->name << ": " << emitted << "\n");
658  if (emitted) {
659  json.attributeBegin("value");
660  json.rawValue([&](auto &os) { os << emitted.expr; });
661  json.attributeEnd();
662  json.attribute("type_name", emitted.type.name);
663  if (auto dims = emitted.type.emitPackedDims(); !dims.empty())
664  json.attribute("packed_range", std::move(dims));
665  if (auto dims = emitted.type.emitUnpackedDims(); !dims.empty())
666  json.attribute("unpacked_range", std::move(dims));
667  }
668 
669  json.objectEnd();
670 }
671 
672 /// Emit the DI expression necessary to materialize a value.
673 EmittedExpr FileEmitter::emitExpression(Value value) {
674  // A few helpers to simplify creating the various JSON operator and expression
675  // snippets.
676  auto hglddSigName = [](StringRef sigName) -> JObject {
677  return JObject{{"sig_name", sigName}};
678  };
679  auto hglddOperator = [](StringRef opcode, JValue args) -> JObject {
680  return JObject{
681  {"opcode", opcode},
682  {"operands", std::move(args)},
683  };
684  };
685  auto hglddInt32 = [](uint32_t value) -> JObject {
686  return JObject({{"integer_num", value}});
687  };
688 
689  if (auto blockArg = dyn_cast<BlockArgument>(value)) {
690  auto module = dyn_cast<hw::HWModuleOp>(blockArg.getOwner()->getParentOp());
691  if (!module)
692  return {};
693  auto name = module.getInputNameAttr(blockArg.getArgNumber());
694  if (!name)
695  return {};
696  return {hglddSigName(name), value.getType()};
697  }
698 
699  auto result = cast<OpResult>(value);
700  auto *op = result.getOwner();
701 
702  // If the operation is a named signal in the output Verilog, use that name.
703  if (isa<hw::WireOp, sv::WireOp, sv::RegOp, sv::LogicOp>(op)) {
704  auto name = op->getAttrOfType<StringAttr>("hw.verilogName");
705  if (!name || name.empty())
706  name = op->getAttrOfType<StringAttr>("name");
707  if (name && !name.empty())
708  return {hglddSigName(name), result.getType()};
709  }
710 
711  // Emit constants directly.
712  if (auto constOp = dyn_cast<hw::ConstantOp>(op)) {
713  // Determine the bit width of the constant.
714  auto type = constOp.getType();
715  auto width = hw::getBitWidth(type);
716 
717  // Emit zero-width constants as a 1-bit zero value. This ensures we get a
718  // proper Verilog-compatible value as a result. Expressions like
719  // concatenation should instead skip zero-width values.
720  if (width < 1)
721  return {JObject({{"bit_vector", "0"}}),
722  IntegerType::get(op->getContext(), 1)};
723 
724  // Serialize the constant as a base-2 binary string.
725  SmallString<64> buffer;
726  buffer.reserve(width);
727  constOp.getValue().toStringUnsigned(buffer, 2);
728 
729  // Pad the string with leading zeros such that it is exactly of the required
730  // width. This is needed since tools will use the string length to determine
731  // the width of the constant.
732  std::reverse(buffer.begin(), buffer.end());
733  while (buffer.size() < (size_t)width)
734  buffer += '0';
735  std::reverse(buffer.begin(), buffer.end());
736  assert(buffer.size() == (size_t)width);
737 
738  return {JObject({{"bit_vector", buffer}}), type};
739  }
740 
741  // Emit structs as assignment patterns and generate corresponding struct
742  // definitions for inclusion in the main "objects" array.
743  if (auto structOp = dyn_cast<debug::StructOp>(op)) {
744  // Collect field names, expressions, and types.
745  auto structNameHintLen = structNameHint.size();
746  std::vector<JValue> values;
747  SmallVector<std::tuple<EmittedType, StringAttr, Location>> types;
748  for (auto [nameAttr, field] :
749  llvm::zip(structOp.getNamesAttr(), structOp.getFields())) {
750  auto name = cast<StringAttr>(nameAttr);
751  structNameHint += '_';
752  structNameHint += name.getValue();
753  if (auto value = emitExpression(field)) {
754  values.push_back(value.expr);
755  types.push_back({value.type, name, field.getLoc()});
756  }
757  structNameHint.resize(structNameHintLen);
758  }
759 
760  // Emit empty structs as 0 `bit`.
761  if (values.empty())
762  return {hglddInt32(0), EmittedType("bit")};
763 
764  // Assemble the struct type definition.
765  JArray fieldDefs;
766  llvm::StringMap<size_t> structNamespace;
767  for (auto [type, name, loc] : types) {
768  JObject fieldDef;
769  fieldDef["var_name"] =
770  std::string(legalizeName(name.getValue(), structNamespace));
771  fieldDef["type_name"] = type.name;
772  if (auto dims = type.emitPackedDims(); !dims.empty())
773  fieldDef["packed_range"] = std::move(dims);
774  if (auto dims = type.emitUnpackedDims(); !dims.empty())
775  fieldDef["unpacked_range"] = std::move(dims);
776  findAndSetLocs(fieldDef, loc);
777  fieldDefs.push_back(std::move(fieldDef));
778  }
779  auto structName = legalizeName(structNameHint, state.objectNamespace);
780  JObject structDef;
781  structDef["kind"] = "struct";
782  structDef["obj_name"] = structName;
783  structDef["port_vars"] = std::move(fieldDefs);
784  findAndSetLocs(structDef, structOp.getLoc());
785 
786  StringRef structNameFinal =
787  structDefs.insert({std::move(structDef), structName}).first->second;
788 
789  return {hglddOperator("'{", values), EmittedType(structNameFinal)};
790  }
791 
792  // Emit arrays as assignment patterns.
793  if (auto arrayOp = dyn_cast<debug::ArrayOp>(op)) {
794  std::vector<JValue> values;
795  EmittedType type;
796  for (auto element : arrayOp.getElements()) {
797  if (auto value = emitExpression(element)) {
798  values.push_back(value.expr);
799  if (type && type != value.type)
800  return {};
801  type = value.type;
802  }
803  }
804 
805  // Emit empty arrays as 0 `bit`.
806  if (!type)
807  return {hglddInt32(0), EmittedType("bit")};
808 
809  type.addUnpackedDim(values.size());
810  return {hglddOperator("'{", values), type};
811  }
812 
813  // Look through read inout ops.
814  if (auto readOp = dyn_cast<sv::ReadInOutOp>(op))
815  return emitExpression(readOp.getInput());
816 
817  // Emit unary and binary combinational ops as their corresponding HGLDD
818  // operation.
819  StringRef unaryOpcode = TypeSwitch<Operation *, StringRef>(op)
820  .Case<comb::ParityOp>([](auto) { return "^"; })
821  .Default([](auto) { return ""; });
822  if (!unaryOpcode.empty() && op->getNumOperands() == 1) {
823  auto arg = emitExpression(op->getOperand(0));
824  if (!arg)
825  return {};
826  return {hglddOperator(unaryOpcode, JArray{arg.expr}), result.getType()};
827  }
828 
829  StringRef binaryOpcode =
830  TypeSwitch<Operation *, StringRef>(op)
831  .Case<comb::AndOp>([](auto) { return "&"; })
832  .Case<comb::OrOp>([](auto) { return "|"; })
833  .Case<comb::XorOp>([](auto) { return "^"; })
834  .Case<comb::AddOp>([](auto) { return "+"; })
835  .Case<comb::SubOp>([](auto) { return "-"; })
836  .Case<comb::MulOp>([](auto) { return "*"; })
837  .Case<comb::DivUOp, comb::DivSOp>([](auto) { return "/"; })
838  .Case<comb::ModUOp, comb::ModSOp>([](auto) { return "%"; })
839  .Case<comb::ShlOp>([](auto) { return "<<"; })
840  .Case<comb::ShrUOp>([](auto) { return ">>"; })
841  .Case<comb::ShrSOp>([](auto) { return ">>>"; })
842  .Case<comb::ICmpOp>([](auto cmpOp) -> StringRef {
843  switch (cmpOp.getPredicate()) {
844  case comb::ICmpPredicate::eq:
845  return "==";
846  case comb::ICmpPredicate::ne:
847  return "!=";
848  case comb::ICmpPredicate::ceq:
849  return "===";
850  case comb::ICmpPredicate::cne:
851  return "!==";
852  case comb::ICmpPredicate::weq:
853  return "==?";
854  case comb::ICmpPredicate::wne:
855  return "!=?";
856  case comb::ICmpPredicate::ult:
857  case comb::ICmpPredicate::slt:
858  return "<";
859  case comb::ICmpPredicate::ugt:
860  case comb::ICmpPredicate::sgt:
861  return ">";
862  case comb::ICmpPredicate::ule:
863  case comb::ICmpPredicate::sle:
864  return "<=";
865  case comb::ICmpPredicate::uge:
866  case comb::ICmpPredicate::sge:
867  return ">=";
868  }
869  return {};
870  })
871  .Default([](auto) { return ""; });
872  if (!binaryOpcode.empty()) {
873  if (op->getNumOperands() != 2) {
874  op->emitOpError("must have two operands for HGLDD emission");
875  return {};
876  }
877  auto lhs = emitExpression(op->getOperand(0));
878  auto rhs = emitExpression(op->getOperand(1));
879  if (!lhs || !rhs)
880  return {};
881  return {hglddOperator(binaryOpcode, {lhs.expr, rhs.expr}),
882  result.getType()};
883  }
884 
885  // Special handling for concatenation.
886  if (auto concatOp = dyn_cast<comb::ConcatOp>(op)) {
887  std::vector<JValue> args;
888  for (auto operand : concatOp.getOperands()) {
889  auto value = emitExpression(operand);
890  if (!value)
891  return {};
892  args.push_back(value.expr);
893  }
894  return {hglddOperator("{}", args), concatOp.getType()};
895  }
896 
897  // Emit `ReplicateOp` as HGLDD `R{}` op.
898  if (auto replicateOp = dyn_cast<comb::ReplicateOp>(op)) {
899  auto arg = emitExpression(replicateOp.getInput());
900  if (!arg)
901  return {};
902  return {hglddOperator("R{}",
903  {
904  hglddInt32(replicateOp.getMultiple()),
905  arg.expr,
906  }),
907  replicateOp.getType()};
908  }
909 
910  // Emit extracts as HGLDD `[]` ops.
911  if (auto extractOp = dyn_cast<comb::ExtractOp>(op)) {
912  auto arg = emitExpression(extractOp.getInput());
913  if (!arg)
914  return {};
915  auto lowBit = extractOp.getLowBit();
916  auto highBit = lowBit + extractOp.getType().getIntOrFloatBitWidth() - 1;
917  return {hglddOperator("[]",
918  {
919  arg.expr,
920  hglddInt32(highBit),
921  hglddInt32(lowBit),
922  }),
923  extractOp.getType()};
924  }
925 
926  // Emit `MuxOp` as HGLDD `?:` ternary op.
927  if (auto muxOp = dyn_cast<comb::MuxOp>(op)) {
928  auto cond = emitExpression(muxOp.getCond());
929  auto lhs = emitExpression(muxOp.getTrueValue());
930  auto rhs = emitExpression(muxOp.getFalseValue());
931  if (!cond || !lhs || !rhs)
932  return {};
933  return {hglddOperator("?:", {cond.expr, lhs.expr, rhs.expr}),
934  muxOp.getType()};
935  }
936 
937  // As a last resort, look for any named wire-like ops this value feeds into.
938  // This is useful for instance output ports for example since we cannot access
939  // instance ports as `<instName>.<portName>` in HGLDD. Instead, we look for a
940  // wire hooked up to the instance output, which is very likely to be present
941  // after Verilog emission.
942  for (auto &use : result.getUses()) {
943  auto *user = use.getOwner();
944  // Use name of `hw.wire` that carries this value.
945  if (auto wireOp = dyn_cast<hw::WireOp>(user))
946  if (wireOp.getInput() == result)
947  return emitExpression(wireOp);
948  // Use name of `sv.assign` destination that is assigned this value.
949  if (auto assignOp = dyn_cast<sv::AssignOp>(user))
950  if (assignOp.getSrc() == result)
951  return emitExpression(assignOp.getDest());
952  // Use module output port name that carries this value.
953  if (isa<hw::OutputOp>(user)) {
954  auto mod = cast<hw::HWModuleLike>(user->getParentOp());
955  auto portName = mod.getPort(mod.getHWModuleType().getPortIdForOutputId(
956  use.getOperandNumber()))
957  .getVerilogName();
958  return {hglddSigName(portName), result.getType()};
959  }
960  }
961 
962  return {};
963 }
964 
965 //===----------------------------------------------------------------------===//
966 // Output Splitting
967 //===----------------------------------------------------------------------===//
968 
969 namespace {
970 
971 /// Contextual information for HGLDD emission shared across multiple HGLDD
972 /// files. This struct is used to determine an initial split of debug info files
973 /// and to distribute work.
974 struct Emitter {
975  GlobalState state;
976  SmallVector<FileEmitter, 0> files;
977  Emitter(Operation *module, const EmitHGLDDOptions &options);
978 };
979 
980 } // namespace
981 
982 Emitter::Emitter(Operation *module, const EmitHGLDDOptions &options)
983  : state(module, options) {
984  // Group the DI modules according to their emitted file path. Modules that
985  // don't have an emitted file path annotated are collected in a separate
986  // group. That group, with a null `StringAttr` key, is emitted into a separate
987  // "global.dd" file.
988  MapVector<StringAttr, FileEmitter> groups;
989  for (auto [moduleName, module] : state.di.moduleNodes) {
990  StringAttr hdlFile;
991  if (module->op)
992  if (auto fileLoc = findBestLocation(module->op->getLoc(), true, false))
993  hdlFile = fileLoc.getFilename();
994  auto &fileEmitter =
995  groups.try_emplace(hdlFile, state, hdlFile).first->second;
996  fileEmitter.modules.push_back(module);
997  state.moduleNames[module] =
998  legalizeName(module->name.getValue(), state.objectNamespace);
999  }
1000 
1001  // Determine the output file names and move the emitters into the `files`
1002  // member.
1003  files.reserve(groups.size());
1004  for (auto &[hdlFile, emitter] : groups) {
1005  emitter.outputFileName = options.outputDirectory;
1006  StringRef fileName = hdlFile ? hdlFile.getValue() : "global";
1007  if (llvm::sys::path::is_absolute(fileName))
1008  emitter.outputFileName = fileName;
1009  else
1010  llvm::sys::path::append(emitter.outputFileName, fileName);
1011  llvm::sys::path::replace_extension(emitter.outputFileName, "dd");
1012  llvm::sys::path::remove_dots(emitter.outputFileName, true);
1013  files.push_back(std::move(emitter));
1014  }
1015 
1016  // Dump some information about the files to be created.
1017  LLVM_DEBUG({
1018  llvm::dbgs() << "HGLDD files:\n";
1019  for (auto &emitter : files) {
1020  llvm::dbgs() << "- " << emitter.outputFileName << " (from "
1021  << emitter.hdlFile << ")\n";
1022  for (auto *module : emitter.modules)
1023  llvm::dbgs() << " - " << module->name << "\n";
1024  }
1025  });
1026 }
1027 
1028 //===----------------------------------------------------------------------===//
1029 // Emission Entry Points
1030 //===----------------------------------------------------------------------===//
1031 
1032 LogicalResult debug::emitHGLDD(Operation *module, llvm::raw_ostream &os,
1033  const EmitHGLDDOptions &options) {
1034  Emitter emitter(module, options);
1035  for (auto &fileEmitter : emitter.files) {
1036  os << "\n// ----- 8< ----- FILE \"" + fileEmitter.outputFileName +
1037  "\" ----- 8< -----\n\n";
1038  fileEmitter.emit(os);
1039  }
1040  return success();
1041 }
1042 
1043 LogicalResult debug::emitSplitHGLDD(Operation *module,
1044  const EmitHGLDDOptions &options) {
1045  Emitter emitter(module, options);
1046 
1047  auto emit = [&](auto &fileEmitter) {
1048  // Open the output file for writing.
1049  std::string errorMessage;
1050  auto output =
1051  mlir::openOutputFile(fileEmitter.outputFileName, &errorMessage);
1052  if (!output) {
1053  module->emitError(errorMessage);
1054  return failure();
1055  }
1056 
1057  // Emit the debug information and keep the file around.
1058  fileEmitter.emit(output->os());
1059  output->keep();
1060  return success();
1061  };
1062 
1063  return mlir::failableParallelForEach(module->getContext(), emitter.files,
1064  emit);
1065 }
assert(baseType &&"element must be base type")
static void emitDims(ArrayRef< Attribute > dims, raw_ostream &os, Location loc, ModuleEmitter &emitter)
Emit a list of packed dimensions.
StringAttr getVerilogInstanceName(DIInstance &inst)
Definition: EmitHGLDD.cpp:562
llvm::json::OStream JOStream
Definition: EmitHGLDD.cpp:38
static void findLocations(Location loc, unsigned level, SmallVectorImpl< FileLineColLoc > &locs)
Walk the given loc and collect file-line-column locations that we want to report as source ("HGL") lo...
Definition: EmitHGLDD.cpp:58
llvm::json::Value JValue
Definition: EmitHGLDD.cpp:35
StringAttr getVerilogModuleName(DIModule &module)
Definition: EmitHGLDD.cpp:555
static bool makePathRelative(StringRef path, StringRef relativeTo, SmallVectorImpl< char > &relativePath)
Make the given path relative to the relativeTo path and store the result in relativePath.
Definition: EmitHGLDD.cpp:123
static FileLineColLoc findBestLocation(Location loc, bool emitted, bool fileMustExist)
Find the best location to report as source location ("HGL", emitted = false) or as emitted location (...
Definition: EmitHGLDD.cpp:81
static StringRef legalizeName(StringRef name, llvm::StringMap< size_t > &nextGeneratedNameIDs)
Legalize the given name such that it only consists of valid identifier characters in Verilog and does...
Definition: EmitHGLDD.cpp:172
llvm::json::Object JObject
Definition: EmitHGLDD.cpp:37
llvm::json::Array JArray
Definition: EmitHGLDD.cpp:36
int32_t width
Definition: FIRRTL.cpp:36
static StringAttr append(StringAttr base, const Twine &suffix)
Return a attribute with the specified suffix appended.
FileEmitter(VerilogEmitterState &state)
void emit(emit::FileOp op)
Direction get(bool isOutput)
Returns an output direction if isOutput is true, otherwise returns an input direction.
Definition: CalyxOps.cpp:53
LogicalResult emitHGLDD(Operation *module, llvm::raw_ostream &os, const EmitHGLDDOptions &options={})
Serialize the debug information in the given module into the HGLDD format and writes it to output.
Definition: EmitHGLDD.cpp:1032
LogicalResult emitSplitHGLDD(Operation *module, const EmitHGLDDOptions &options={})
Serialize the debug information in the given module into the HGLDD format and emit one companion HGLD...
Definition: EmitHGLDD.cpp:1043
T & operator<<(T &os, FIRVersion version)
Definition: FIRParser.h:115
std::optional< int64_t > getBitWidth(FIRRTLBaseType type, bool ignoreFlip=false)
bool isHWIntegerType(mlir::Type type)
Return true if the specified type is a value HW Integer type.
Definition: HWTypes.cpp:52
mlir::Type getCanonicalType(mlir::Type type)
Definition: HWTypes.cpp:41
evaluator::ObjectValue Object
Definition: Evaluator.h:411
This file defines an intermediate representation for circuits acting as an abstraction for constraint...
Definition: DebugAnalysis.h:21
Definition: debug.py:1
mlir::raw_indented_ostream & dbgs()
Definition: Utility.h:28
llvm::hash_code hash_value(const T &e)
Operation * op
The operation that generated this instance.
Definition: DebugInfo.h:43
StringAttr name
The name of this instance.
Definition: DebugInfo.h:45
DIModule * module
The instantiated module.
Definition: DebugInfo.h:47
bool isInline
If this is an inline scope created by a dbg.scope operation.
Definition: DebugInfo.h:38
Operation * op
The operation that generated this level of hierarchy.
Definition: DebugInfo.h:28
SmallVector< DIInstance *, 0 > instances
Levels of hierarchy nested under this module.
Definition: DebugInfo.h:32
SmallVector< DIVariable *, 0 > variables
Variables declared within this module.
Definition: DebugInfo.h:34
bool isExtern
If this is an extern declaration.
Definition: DebugInfo.h:36
StringAttr name
The name of this level of hierarchy.
Definition: DebugInfo.h:30
Value value
The SSA value representing the value of this variable.
Definition: DebugInfo.h:56
StringAttr name
The name of this variable.
Definition: DebugInfo.h:52
LocationAttr loc
The location of the variable's declaration.
Definition: DebugInfo.h:54
Debug information attached to an operation and the operations nested within.
Definition: DebugInfo.h:63
Options for HGLDD emission.
Definition: DebugInfo.h:29
static unsigned getHashValue(const JValue &x)
Definition: EmitHGLDD.cpp:109
static bool isEqual(const JValue &a, const JValue &b)
Definition: EmitHGLDD.cpp:114
static JValue getEmptyKey()
Definition: EmitHGLDD.cpp:107
static JValue getTombstoneKey()
Definition: EmitHGLDD.cpp:108