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