CIRCT 20.0.0git
Loading...
Searching...
No Matches
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
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
28using namespace mlir;
29using namespace circt;
30using namespace debug;
31
32using llvm::MapVector;
33using llvm::SmallMapVector;
34
35using JValue = llvm::json::Value;
36using JArray = llvm::json::Array;
37using JObject = llvm::json::Object;
38using 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".
58static 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.
81static 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.
104namespace llvm {
105template <>
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.
123static 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.
172static 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
181namespace {
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.
186struct 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.
209struct 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.
246struct EmittedExpr {
247 JValue expr = nullptr;
248 EmittedType type;
249 operator bool() const { return expr != nullptr && type; }
250};
251
252[[maybe_unused]] 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[[maybe_unused]] 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.
275struct 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.
399unsigned 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
473void 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
534JValue 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
555StringAttr 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`.
570void 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.
589void 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`.
601void 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`.
641void 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.
673EmittedExpr 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
969namespace {
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.
974struct Emitter {
975 GlobalState state;
976 SmallVector<FileEmitter, 0> files;
977 Emitter(Operation *module, const EmitHGLDDOptions &options);
978};
979
980} // namespace
981
982Emitter::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
1032LogicalResult 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
1043LogicalResult 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)
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
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.
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...
llvm::json::Object JObject
Definition EmitHGLDD.cpp:37
llvm::json::Array JArray
Definition EmitHGLDD.cpp:36
void emit(emit::FileOp op)
void addDefinitions(mlir::Operation *top)
Populate the symbol cache with all symbol-defining operations within the 'top' operation.
Definition SymCache.cpp:23
This stores lookup tables to make manipulating and working with the IR more efficient.
Definition HWSymCache.h:27
void freeze()
Mark the cache as frozen, which allows it to be shared across threads.
Definition HWSymCache.h:75
StringRef getVerilogModuleName(Operation *module)
Definition HWOps.h:54
OS & operator<<(OS &os, const InnerSymTarget &target)
Printing InnerSymTarget's.
static llvm::hash_code hash_value(const ModulePort &port)
Definition HWTypes.h:38
The InstanceGraph op interface, see InstanceGraphInterface.td for more details.
Definition debug.py:1
Definition emit.py:1
Operation * op
The operation that generated this instance.
Definition DebugInfo.h:43
StringAttr name
The name of this instance.
Definition DebugInfo.h:45
Operation * op
The operation that generated this level of hierarchy.
Definition DebugInfo.h:28
bool isExtern
If this is an extern declaration.
Definition DebugInfo.h:36
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)
static bool isEqual(const JValue &a, const JValue &b)
static JValue getTombstoneKey()