CIRCT 23.0.0git
Loading...
Searching...
No Matches
FIRRTLAnnotationHelper.cpp
Go to the documentation of this file.
1//===- FIRRTLAnnotationHelper.cpp - FIRRTL Annotation Lookup ----*- C++ -*-===//
2//
3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4// See https://llvm.org/LICENSE.txt for license information.
5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6//
7//===----------------------------------------------------------------------===//
8//
9// This file implements helpers mapping annotations to operations.
10//
11//===----------------------------------------------------------------------===//
12
16#include "mlir/IR/ImplicitLocOpBuilder.h"
17#include "llvm/Support/Debug.h"
18
19#define DEBUG_TYPE "lower-annos"
20
21using namespace circt;
22using namespace firrtl;
23using namespace chirrtl;
24
25using llvm::StringRef;
26
27// Some types have been expanded so the first layer of aggregate path is
28// a return value.
29static LogicalResult updateExpandedPort(StringRef field, AnnoTarget &ref) {
30 if (auto mem = dyn_cast<MemOp>(ref.getOp()))
31 for (size_t p = 0, pe = mem.getPortNames().size(); p < pe; ++p)
32 if (mem.getPortName(p) == field) {
33 ref = PortAnnoTarget(mem, p);
34 return success();
35 }
36 ref.getOp()->emitError("Cannot find port with name ") << field;
37 return failure();
38}
39
40/// Try to resolve an non-array aggregate name from a target given the type and
41/// operation of the resolved target. This needs to deal with places where we
42/// represent bundle returns as split into constituent parts.
43static FailureOr<unsigned> findBundleElement(Operation *op, Type type,
44 StringRef field) {
45 auto bundle = type_dyn_cast<BundleType>(type);
46 if (!bundle) {
47 op->emitError("field access '")
48 << field << "' into non-bundle type '" << type << "'";
49 return failure();
50 }
51 auto idx = bundle.getElementIndex(field);
52 if (!idx) {
53 op->emitError("cannot resolve field '")
54 << field << "' in subtype '" << bundle << "'";
55 return failure();
56 }
57 return *idx;
58}
59
60/// Try to resolve an array index from a target given the type of the resolved
61/// target.
62static FailureOr<unsigned> findVectorElement(Operation *op, Type type,
63 StringRef indexStr) {
64 size_t index;
65 if (indexStr.getAsInteger(10, index)) {
66 op->emitError("Cannot convert '") << indexStr << "' to an integer";
67 return failure();
68 }
69 auto vec = type_dyn_cast<FVectorType>(type);
70 if (!vec) {
71 op->emitError("index access '")
72 << index << "' into non-vector type '" << type << "'";
73 return failure();
74 }
75 return index;
76}
77
78static FailureOr<unsigned> findFieldID(AnnoTarget &ref,
79 ArrayRef<TargetToken> tokens) {
80 if (tokens.empty())
81 return 0;
82
83 auto *op = ref.getOp();
84 auto fieldIdx = 0;
85 // The first field for some ops refers to expanded return values.
86 if (isa<MemOp>(op)) {
87 if (failed(updateExpandedPort(tokens.front().name, ref)))
88 return {};
89 tokens = tokens.drop_front();
90 }
91
92 auto type = ref.getType();
93 if (!type)
94 return op->emitError(tokens.front().isIndex ? "index" : "field")
95 << " access in annotation not supported for this operation";
96
97 for (auto token : tokens) {
98 if (token.isIndex) {
99 auto result = findVectorElement(op, type, token.name);
100 if (failed(result))
101 return failure();
102 auto vector = type_cast<FVectorType>(type);
103 type = vector.getElementType();
104 fieldIdx += vector.getFieldID(*result);
105 } else {
106 auto result = findBundleElement(op, type, token.name);
107 if (failed(result))
108 return failure();
109 auto bundle = type_cast<BundleType>(type);
110 type = bundle.getElementType(*result);
111 fieldIdx += bundle.getFieldID(*result);
112 }
113 }
114 return fieldIdx;
115}
116
117void TokenAnnoTarget::toVector(SmallVectorImpl<char> &out) const {
118 out.push_back('~');
119 out.append(circuit.begin(), circuit.end());
120 out.push_back('|');
121 for (auto modInstPair : instances) {
122 out.append(modInstPair.first.begin(), modInstPair.first.end());
123 out.push_back('/');
124 out.append(modInstPair.second.begin(), modInstPair.second.end());
125 out.push_back(':');
126 }
127 out.append(module.begin(), module.end());
128 if (name.empty())
129 return;
130 out.push_back('>');
131 out.append(name.begin(), name.end());
132 for (auto comp : component) {
133 out.push_back(comp.isIndex ? '[' : '.');
134 out.append(comp.name.begin(), comp.name.end());
135 if (comp.isIndex)
136 out.push_back(']');
137 }
138}
139
140std::string firrtl::canonicalizeTarget(StringRef target) {
141
142 if (target.empty())
143 return target.str();
144
145 // If this is a normal Target (not a Named), erase that field in the JSON
146 // object and return that Target.
147 if (target[0] == '~')
148 return target.str();
149
150 // This is a legacy target using the firrtl.annotations.Named type. This
151 // can be trivially canonicalized to a non-legacy target, so we do it with
152 // the following three mappings:
153 // 1. CircuitName => CircuitTarget, e.g., A -> ~A
154 // 2. ModuleName => ModuleTarget, e.g., A.B -> ~A|B
155 // 3. ComponentName => ReferenceTarget, e.g., A.B.C -> ~A|B>C
156 std::string newTarget = ("~" + target).str();
157 auto n = newTarget.find('.');
158 if (n != std::string::npos)
159 newTarget[n] = '|';
160 n = newTarget.find('.');
161 if (n != std::string::npos)
162 newTarget[n] = '>';
163 return newTarget;
164}
165
166std::optional<AnnoPathValue>
168 SymbolTable &symTbl, CircuitTargetCache &cache) {
169 // Validate circuit name.
170 if (!path.circuit.empty() && circuit.getName() != path.circuit) {
171 mlir::emitError(circuit.getLoc())
172 << "circuit name doesn't match annotation '" << path.circuit << '\'';
173 return {};
174 }
175 // Circuit only target.
176 if (path.module.empty()) {
177 assert(path.name.empty() && path.instances.empty() &&
178 path.component.empty());
179 return AnnoPathValue(circuit);
180 }
181
182 // Resolve all instances for non-local paths.
183 SmallVector<InstanceOp> instances;
184 for (auto p : path.instances) {
185 auto mod = symTbl.lookup<FModuleOp>(p.first);
186 if (!mod) {
187 mlir::emitError(circuit.getLoc())
188 << "module doesn't exist '" << p.first << '\'';
189 return {};
190 }
191 auto resolved = cache.lookup(mod, p.second);
192 if (!resolved || !isa<InstanceOp>(resolved.getOp())) {
193 mlir::emitError(circuit.getLoc()) << "cannot find instance '" << p.second
194 << "' in '" << mod.getName() << "'";
195 return {};
196 }
197 instances.push_back(cast<InstanceOp>(resolved.getOp()));
198 }
199 // The final module is where the named target is (or is the named target).
200 auto mod = symTbl.lookup<FModuleLike>(path.module);
201 if (!mod) {
202 mlir::emitError(circuit.getLoc())
203 << "module doesn't exist '" << path.module << '\'';
204 return {};
205 }
206
207 // ClassOps may not participate in annotation targeting. Neither the class
208 // itself, nor any "named thing" defined under it, may be targeted by an anno.
209 if (isa<ClassOp>(mod)) {
210 mlir::emitError(mod.getLoc()) << "annotations cannot target classes";
211 return {};
212 }
213
214 AnnoTarget ref;
215 if (path.name.empty()) {
216 assert(path.component.empty());
217 ref = OpAnnoTarget(mod);
218 } else {
219 ref = cache.lookup(mod, path.name);
220 if (!ref) {
221 mlir::emitError(circuit.getLoc()) << "cannot find name '" << path.name
222 << "' in " << mod.getModuleName();
223 return {};
224 }
225 // AnnoTarget::getType() is not safe (CHIRRTL ops crash, null if instance),
226 // avoid. For now, only references in ports can be targets, check that.
227 // TODO: containsReference().
228 if (isa<PortAnnoTarget>(ref) && isa<RefType>(ref.getType())) {
229 mlir::emitError(circuit.getLoc())
230 << "cannot target reference-type '" << path.name << "' in "
231 << mod.getModuleName();
232 return {};
233 }
234 }
235
236 // If the reference is pointing to an instance op, we have to move the target
237 // to the module. This is done both because it is logical to have one
238 // representation (this effectively canonicalizes a reference target on an
239 // instance into an instance target) and because the SFC has a pass that does
240 // this conversion. E.g., this is converting (where "bar" is an instance):
241 // ~Foo|Foo>bar
242 // Into:
243 // ~Foo|Foo/bar:Bar
244 ArrayRef<TargetToken> component(path.component);
245 if (auto instance = dyn_cast<InstanceOp>(ref.getOp())) {
246 instances.push_back(instance);
247 auto target = cast<FModuleLike>(instance.getReferencedOperation(symTbl));
248 if (component.empty()) {
249 ref = OpAnnoTarget(target);
250 } else if (component.front().isIndex) {
251 mlir::emitError(circuit.getLoc())
252 << "illegal target '" << path.str() << "' indexes into an instance";
253 return {};
254 } else {
255 auto field = component.front().name;
256 ref = AnnoTarget();
257 for (size_t p = 0, pe = getNumPorts(target); p < pe; ++p)
258 if (target.getPortName(p) == field) {
259 ref = PortAnnoTarget(target, p);
260 break;
261 }
262 if (!ref) {
263 mlir::emitError(circuit.getLoc())
264 << "cannot find port '" << field << "' in module "
265 << target.getModuleName();
266 return {};
267 }
268 // TODO: containsReference().
269 if (isa<RefType>(ref.getType())) {
270 mlir::emitError(circuit.getLoc())
271 << "annotation cannot target reference-type port '" << field
272 << "' in module " << target.getModuleName();
273 return {};
274 }
275 component = component.drop_front();
276 }
277 }
278
279 // If we have aggregate specifiers, resolve those now. This call can update
280 // the ref to target a port of a memory.
281 auto result = findFieldID(ref, component);
282 if (failed(result))
283 return {};
284 auto fieldIdx = *result;
285
286 return AnnoPathValue(instances, ref, fieldIdx);
287}
288
289/// split a target string into it constituent parts. This is the primary parser
290/// for targets.
291std::optional<TokenAnnoTarget> firrtl::tokenizePath(StringRef origTarget) {
292 // An empty string is not a legal target.
293 if (origTarget.empty())
294 return {};
295 StringRef target = origTarget;
296 TokenAnnoTarget retval;
297 std::tie(retval.circuit, target) = target.split('|');
298 if (!retval.circuit.empty() && retval.circuit[0] == '~')
299 retval.circuit = retval.circuit.drop_front();
300 while (target.count(':')) {
301 StringRef nla;
302 std::tie(nla, target) = target.split(':');
303 StringRef inst, mod;
304 std::tie(mod, inst) = nla.split('/');
305 retval.instances.emplace_back(mod, inst);
306 }
307 // remove aggregate
308 auto targetBase =
309 target.take_until([](char c) { return c == '.' || c == '['; });
310 auto aggBase = target.drop_front(targetBase.size());
311 std::tie(retval.module, retval.name) = targetBase.split('>');
312 while (!aggBase.empty()) {
313 if (aggBase[0] == '.') {
314 aggBase = aggBase.drop_front();
315 StringRef field = aggBase.take_front(aggBase.find_first_of("[."));
316 aggBase = aggBase.drop_front(field.size());
317 retval.component.push_back({field, false});
318 } else if (aggBase[0] == '[') {
319 aggBase = aggBase.drop_front();
320 StringRef index = aggBase.take_front(aggBase.find_first_of(']'));
321 aggBase = aggBase.drop_front(index.size() + 1);
322 retval.component.push_back({index, true});
323 } else {
324 return {};
325 }
326 }
327
328 return retval;
329}
330
331std::optional<AnnoPathValue> firrtl::resolvePath(StringRef rawPath,
332 CircuitOp circuit,
333 SymbolTable &symTbl,
334 CircuitTargetCache &cache) {
335 auto pathStr = canonicalizeTarget(rawPath);
336 StringRef path{pathStr};
337
338 auto tokens = tokenizePath(path);
339 if (!tokens) {
340 mlir::emitError(circuit.getLoc())
341 << "Cannot tokenize annotation path " << rawPath;
342 return {};
343 }
344
345 return resolveEntities(*tokens, circuit, symTbl, cache);
346}
347
348//===----------------------------------------------------------------------===//
349// AnnoTargetCache
350//===----------------------------------------------------------------------===//
351
352void AnnoTargetCache::gatherTargets(FModuleLike mod) {
353 // Add ports
354 for (const auto &p : llvm::enumerate(mod.getPorts()))
355 insertPort(mod, p.index());
356
357 // And named things
358 mod.walk([&](Operation *op) { insertOp(op); });
359}
360
361//===----------------------------------------------------------------------===//
362// HierPathOpCache
363//===----------------------------------------------------------------------===//
364
365HierPathCache::HierPathCache(Operation *op, SymbolTable &symbolTable)
366 : builder(OpBuilder::atBlockBegin(&op->getRegion(0).front())),
367 symbolTable(symbolTable) {
368
369 // Populate the cache with any symbols preexisting.
370 for (auto &region : op->getRegions())
371 for (auto &block : region.getBlocks())
372 for (auto path : block.getOps<hw::HierPathOp>())
373 cache[path.getNamepathAttr()] = path;
374}
375
376hw::HierPathOp HierPathCache::getOpFor(ArrayAttr attr) {
377 auto &op = cache[attr];
378 if (!op) {
379 op = hw::HierPathOp::create(builder, UnknownLoc::get(builder.getContext()),
380 "nla", attr);
381 symbolTable.insert(op);
382 op.setVisibility(SymbolTable::Visibility::Private);
383 }
384 return op;
385}
386
387//===----------------------------------------------------------------------===//
388// Code related to handling Grand Central Mem Taps annotations
389//===----------------------------------------------------------------------===//
390
392 DictionaryAttr anno,
393 ApplyState &state) {
394 auto loc = state.circuit.getLoc();
395
396 auto sourceAttr =
397 tryGetAs<StringAttr>(anno, anno, "source", loc, memTapAnnoClass);
398 if (!sourceAttr)
399 return failure();
400
401 auto sourceTargetStr = canonicalizeTarget(sourceAttr.getValue());
402 std::optional<AnnoPathValue> srcTarget = resolvePath(
403 sourceTargetStr, state.circuit, state.symTbl, state.targetCaches);
404 if (!srcTarget)
405 return mlir::emitError(loc, "cannot resolve source target path '")
406 << sourceTargetStr << "'";
407
408 auto tapsAttr = tryGetAs<ArrayAttr>(anno, anno, "sink", loc, memTapAnnoClass);
409 if (!tapsAttr || tapsAttr.empty())
410 return mlir::emitError(loc, "sink must have at least one entry");
411
412 auto tap = dyn_cast_or_null<StringAttr>(tapsAttr[0]);
413 if (!tap) {
414 return mlir::emitError(
415 loc, "Annotation '" + Twine(memTapAnnoClass) +
416 "' with path '.taps[0" +
417 "]' contained an unexpected type (expected a string).")
418 .attachNote()
419 << "The full Annotation is reprodcued here: " << anno << "\n";
420 }
421
422 auto wireTargetStr = canonicalizeTarget(tap.getValue());
423 if (!tokenizePath(wireTargetStr))
424 return failure();
425 std::optional<AnnoPathValue> wireTarget = resolvePath(
426 wireTargetStr, state.circuit, state.symTbl, state.targetCaches);
427 if (!wireTarget)
428 return mlir::emitError(loc, "Annotation '" + Twine(memTapAnnoClass) +
429 "' with path '.taps[0]' contains target '" +
430 wireTargetStr +
431 "' that cannot be resolved.")
432 .attachNote()
433 << "The full Annotation is reproduced here: " << anno << "\n";
434
435 auto combMem = dyn_cast<chirrtl::CombMemOp>(srcTarget->ref.getOp());
436 if (!combMem)
437 return srcTarget->ref.getOp()->emitOpError(
438 "unsupported operation, only CombMem can be used as the source of "
439 "MemTap");
440 if (!combMem.getType().getElementType().isGround())
441 return combMem.emitOpError(
442 "cannot generate MemTap to a memory with aggregate data type");
443 if (tapsAttr.size() != combMem.getType().getNumElements())
444 return mlir::emitError(
445 loc, "sink cannot specify more taps than the depth of the memory");
446 if (srcTarget->instances.empty()) {
447 auto path = state.instancePathCache.getAbsolutePaths(
448 combMem->getParentOfType<FModuleOp>());
449 if (path.size() > 1)
450 return combMem.emitOpError(
451 "cannot be resolved as source for MemTap, multiple paths from top "
452 "exist and unique instance cannot be resolved");
453 srcTarget->instances.append(path.back().begin(), path.back().end());
454 }
455
456 ImplicitLocOpBuilder builder(combMem->getLoc(), combMem);
457
458 // Lower memory taps to real ports on the memory. This is done if the taps
459 // are supposed to be synthesized.
460 if (state.noRefTypePorts) {
461 // Create new ports _after_ all other ports to avoid permuting existing
462 // ports.
463 builder.setInsertionPointToEnd(
464 combMem->getParentOfType<FModuleOp>().getBodyBlock());
465
466 // Determine the clock to use for the debug ports. Error if the same clock
467 // is not used for all ports or if no clock port is found.
468 Value clock;
469 for (auto *portOp : combMem.getResult().getUsers()) {
470 for (auto result : portOp->getResults()) {
471 for (auto *user : result.getUsers()) {
472 auto accessOp = dyn_cast<chirrtl::MemoryPortAccessOp>(user);
473 if (!accessOp)
474 continue;
475 auto newClock = accessOp.getClock();
476 if (clock && clock != newClock)
477 return combMem.emitOpError(
478 "has different clocks on different ports (this is ambiguous "
479 "when compiling without reference types)");
480 clock = newClock;
481 }
482 }
483 }
484 if (!clock)
485 return combMem.emitOpError(
486 "does not have an access port to determine a clock connection (this "
487 "is necessary when compiling without reference types)");
488
489 // Add one port per memory address.
490 SmallVector<Value> data;
491 Type uintType = builder.getType<UIntType>();
492 for (uint64_t i = 0, e = combMem.getType().getNumElements(); i != e; ++i) {
493 auto port = chirrtl::MemoryPortOp::create(
494 builder, combMem.getType().getElementType(),
495 CMemoryPortType::get(builder.getContext()), combMem.getResult(),
496 MemDirAttr::Read, builder.getStringAttr("memTap_" + Twine(i)),
497 builder.getArrayAttr({}));
498 chirrtl::MemoryPortAccessOp::create(
499 builder, port.getPort(),
500 ConstantOp::create(builder, uintType, APSInt::getUnsigned(i)), clock);
501 data.push_back(port.getData());
502 }
503
504 // Package up all the reads into a vector.
505 auto sendVal = VectorCreateOp::create(
506 builder,
507 FVectorType::get(combMem.getType().getElementType(),
508 combMem.getType().getNumElements()),
509 data);
510 auto sink = wireTarget->ref.getOp()->getResult(0);
511
512 // Add a wiring problem to hook up the vector to the destination wire.
513 state.wiringProblems.push_back(
514 {sendVal, sink, "memTap", WiringProblem::RefTypeUsage::Never});
515 return success();
516 }
517
518 // Normal memory handling. Create a debug port.
519 builder.setInsertionPointAfter(combMem);
520 // Construct the type for the debug port.
521 auto debugType = RefType::get(FVectorType::get(
522 combMem.getType().getElementType(), combMem.getType().getNumElements()));
523 Value memDbgPort =
524 chirrtl::MemoryDebugPortOp::create(
525 builder, debugType, combMem,
526 state.getNamespace(srcTarget->ref.getModule()).newName("memTap"))
527 .getResult();
528
529 auto sendVal = memDbgPort;
530 if (wireTarget->ref.getOp()->getResult(0).getType() !=
531 type_cast<RefType>(sendVal.getType()).getType())
532 return wireTarget->ref.getOp()->emitError(
533 "cannot generate the MemTap, wiretap Type does not match the memory "
534 "type");
535 auto sink = wireTarget->ref.getOp()->getResult(0);
536 state.wiringProblems.push_back(
537 {sendVal, sink, "memTap", WiringProblem::RefTypeUsage::Prefer});
538 return success();
539}
assert(baseType &&"element must be base type")
static FailureOr< unsigned > findVectorElement(Operation *op, Type type, StringRef indexStr)
Try to resolve an array index from a target given the type of the resolved target.
static FailureOr< unsigned > findFieldID(AnnoTarget &ref, ArrayRef< TargetToken > tokens)
static FailureOr< unsigned > findBundleElement(Operation *op, Type type, StringRef field)
Try to resolve an non-array aggregate name from a target given the type and operation of the resolved...
static LogicalResult updateExpandedPort(StringRef field, AnnoTarget &ref)
StringRef newName(const Twine &name)
Return a unique name, derived from the input name, and add the new name to the internal namespace.
Definition Namespace.h:87
std::optional< AnnoPathValue > resolveEntities(TokenAnnoTarget path, CircuitOp circuit, SymbolTable &symTbl, CircuitTargetCache &cache)
Convert a parsed target string to a resolved target structure.
size_t getNumPorts(Operation *op)
Return the number of ports in a module-like thing (modules, memories, etc)
std::string canonicalizeTarget(StringRef target)
Return an input target string in canonical form.
LogicalResult applyGCTMemTaps(const AnnoPathValue &target, DictionaryAttr anno, ApplyState &state)
std::optional< AnnoPathValue > resolvePath(StringRef rawPath, CircuitOp circuit, SymbolTable &symTbl, CircuitTargetCache &cache)
Resolve a string path to a named item inside a circuit.
std::optional< TokenAnnoTarget > tokenizePath(StringRef origTarget)
Parse a FIRRTL annotation path into its constituent parts.
The InstanceGraph op interface, see InstanceGraphInterface.td for more details.
void gatherTargets(FModuleLike mod)
Walk the module and add named things to 'targets'.
void insertPort(FModuleLike mod, size_t portNo)
Add a new module port to the target cache.
An annotation target is used to keep track of something that is targeted by an Annotation.
FIRRTLType getType() const
Get the type of the target.
State threaded through functions for resolving and applying annotations.
SmallVector< WiringProblem > wiringProblems
InstancePathCache & instancePathCache
hw::InnerSymbolNamespace & getNamespace(FModuleLike module)
Cache AnnoTargets for a circuit's modules, walked as needed.
AnnoTarget lookup(FModuleLike module, StringRef name)
Lookup the target for 'name' in 'module'.
HierPathCache(Operation *op, SymbolTable &symbolTable)
DenseMap< ArrayAttr, hw::HierPathOp > cache
hw::HierPathOp getOpFor(ArrayAttr attr)
This represents an annotation targeting a specific operation.
This represents an annotation targeting a specific port of a module, memory, or instance.
The parsed annotation path.
SmallVector< TargetToken > component
std::string str() const
Convert the annotation path to a string.
void toVector(SmallVectorImpl< char > &out) const
Append the annotation path to the given SmallString or SmallVector.
SmallVector< std::pair< StringRef, StringRef > > instances
ArrayRef< InstancePath > getAbsolutePaths(ModuleOpInterface op)