CIRCT 20.0.0git
Loading...
Searching...
No Matches
AddSeqMemPorts.cpp
Go to the documentation of this file.
1//===- AddSeqMemPorts.cpp - Add extra ports to memories ---------*- 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// This file defines the AddSeqMemPorts pass. This pass will add extra ports
9// to memory modules.
10//
11//===----------------------------------------------------------------------===//
12
24#include "mlir/Pass/Pass.h"
25#include "llvm/ADT/PostOrderIterator.h"
26#include "llvm/ADT/STLFunctionalExtras.h"
27#include "llvm/Support/Path.h"
28
29namespace circt {
30namespace firrtl {
31#define GEN_PASS_DEF_ADDSEQMEMPORTS
32#include "circt/Dialect/FIRRTL/Passes.h.inc"
33} // namespace firrtl
34} // namespace circt
35
36using namespace circt;
37using namespace firrtl;
38
39namespace {
40struct AddSeqMemPortsPass
41 : public circt::firrtl::impl::AddSeqMemPortsBase<AddSeqMemPortsPass> {
42 void runOnOperation() override;
43 LogicalResult processAddPortAnno(Location loc, Annotation anno);
44 LogicalResult processFileAnno(Location loc, StringRef metadataDir,
45 Annotation anno);
46 LogicalResult processAnnos(CircuitOp circuit);
47 void createOutputFile(igraph::ModuleOpInterface moduleOp);
48 LogicalResult processMemModule(FMemModuleOp mem);
49 LogicalResult processModule(FModuleOp moduleOp);
50
51 /// Get the cached namespace for a module.
52 hw::InnerSymbolNamespace &getModuleNamespace(FModuleLike moduleOp) {
53 return moduleNamespaces.try_emplace(moduleOp, moduleOp).first->second;
54 }
55
56 /// Obtain an inner reference to an operation, possibly adding an `inner_sym`
57 /// to that operation.
58 hw::InnerRefAttr getInnerRefTo(Operation *op) {
59 return ::getInnerRefTo(op,
60 [&](FModuleLike mod) -> hw::InnerSymbolNamespace & {
61 return getModuleNamespace(mod);
62 });
63 }
64
65 /// This represents the collected information of the memories in a module.
66 struct MemoryInfo {
67 /// The list of extra ports added to this module to support additional ports
68 /// on all memories underneath this module.
69 std::vector<std::pair<unsigned, PortInfo>> extraPorts;
70
71 /// This is an ordered list of instance paths to all memories underneath
72 /// this module. This will record each memory once for every port added,
73 /// which is for some reason the format of the metadata file.
74 std::vector<SmallVector<Attribute>> instancePaths;
75 };
76
77 CircuitNamespace circtNamespace;
78 /// This maps a module to information about the memories.
79 DenseMap<Operation *, MemoryInfo> memInfoMap;
80 DenseMap<Attribute, Operation *> innerRefToInstanceMap;
81
82 InstanceGraph *instanceGraph;
83 InstanceInfo *instanceInfo;
84
85 /// If the metadata output file was specified in an annotation.
86 StringAttr outputFile;
87
88 /// This is the list of every port to be added to each sequential memory.
89 SmallVector<PortInfo> userPorts;
90 /// This is an attribute holding the metadata for extra ports.
91 ArrayAttr extraPortsAttr;
92
93 /// Cached module namespaces.
94 DenseMap<Operation *, hw::InnerSymbolNamespace> moduleNamespaces;
95
96 bool anythingChanged;
97};
98} // end anonymous namespace
99
100LogicalResult AddSeqMemPortsPass::processAddPortAnno(Location loc,
101 Annotation anno) {
102 auto name = anno.getMember<StringAttr>("name");
103 if (!name)
104 return emitError(
105 loc, "AddSeqMemPortAnnotation requires field 'name' of string type");
106
107 auto input = anno.getMember<BoolAttr>("input");
108 if (!input)
109 return emitError(
110 loc, "AddSeqMemPortAnnotation requires field 'input' of boolean type");
111 auto direction = input.getValue() ? Direction::In : Direction::Out;
112
113 auto width = anno.getMember<IntegerAttr>("width");
114 if (!width)
115 return emitError(
116 loc, "AddSeqMemPortAnnotation requires field 'width' of integer type");
117 auto type = UIntType::get(&getContext(), width.getInt());
118 userPorts.push_back({name, type, direction});
119 return success();
120}
121
122LogicalResult AddSeqMemPortsPass::processFileAnno(Location loc,
123 StringRef metadataDir,
124 Annotation anno) {
125 if (outputFile)
126 return emitError(
127 loc, "circuit has two AddSeqMemPortsFileAnnotation annotations");
128
129 auto filename = anno.getMember<StringAttr>("filename");
130 if (!filename)
131 return emitError(loc,
132 "AddSeqMemPortsFileAnnotation requires field 'filename' "
133 "of string type");
134
135 SmallString<128> outputFilePath(metadataDir);
136 llvm::sys::path::append(outputFilePath, filename.getValue());
137 outputFile = StringAttr::get(&getContext(), outputFilePath);
138 return success();
139}
140
141LogicalResult AddSeqMemPortsPass::processAnnos(CircuitOp circuit) {
142 auto loc = circuit.getLoc();
143
144 // Find the metadata directory.
145 auto dirAnno =
147 StringRef metadataDir = "metadata";
148 if (dirAnno) {
149 auto dir = dirAnno.getMember<StringAttr>("dirname");
150 if (!dir)
151 return emitError(loc, "MetadataDirAnnotation requires field 'dirname' of "
152 "string type");
153 metadataDir = dir.getValue();
154 }
155
156 // Remove the annotations we care about.
157 bool error = false;
159 if (error)
160 return false;
162 error = failed(processAddPortAnno(loc, anno));
163 return true;
164 }
166 error = failed(processFileAnno(loc, metadataDir, anno));
167 return true;
168 }
169 return false;
170 });
171 return failure(error);
172}
173
174LogicalResult AddSeqMemPortsPass::processMemModule(FMemModuleOp mem) {
175 // Error if all instances are not under the effective DUT.
176 if (!instanceInfo->allInstancesInEffectiveDesign(mem)) {
177 auto diag = mem->emitOpError()
178 << "cannot have ports added to it because it is instantiated "
179 "both under and not under the design-under-test (DUT)";
180 for (auto *instNode : instanceGraph->lookup(mem)->uses()) {
181 auto instanceOp = instNode->getInstance();
182 if (auto layerBlockOp = instanceOp->getParentOfType<LayerBlockOp>()) {
183 diag.attachNote(instanceOp.getLoc())
184 << "this instance is under a layer block";
185 diag.attachNote(layerBlockOp.getLoc())
186 << "the innermost layer block is here";
187 continue;
188 }
189 if (!instanceInfo->allInstancesInEffectiveDesign(
190 instNode->getParent()->getModule())) {
191 diag.attachNote(instanceOp.getLoc())
192 << "this instance is inside a module that is instantiated outside "
193 "the design";
194 }
195 }
196 return failure();
197 }
198
199 // We have to add the user ports to every mem module.
200 size_t portIndex = mem.getNumPorts();
201 auto &memInfo = memInfoMap[mem];
202 auto &extraPorts = memInfo.extraPorts;
203 for (auto const &p : userPorts)
204 extraPorts.emplace_back(portIndex, p);
205 mem.insertPorts(extraPorts);
206 // Attach the extraPorts metadata.
207 mem.setExtraPortsAttr(extraPortsAttr);
208 return success();
209}
210
211LogicalResult AddSeqMemPortsPass::processModule(FModuleOp moduleOp) {
212 auto *context = &getContext();
213 auto builder = OpBuilder(moduleOp.getContext());
214 auto &memInfo = memInfoMap[moduleOp];
215 auto &extraPorts = memInfo.extraPorts;
216 // List of ports added to submodules which must be connected to this module's
217 // ports.
218 SmallVector<Value> values;
219
220 // The base index to use when adding ports to the current module.
221 unsigned firstPortIndex = moduleOp.getNumPorts();
222
223 auto result = moduleOp.walk([&](Operation *op) {
224 if (auto inst = dyn_cast<InstanceOp>(op)) {
225 auto submodule = inst.getReferencedModule(*instanceGraph);
226
227 auto subMemInfoIt = memInfoMap.find(submodule);
228 // If there are no extra ports, we don't have to do anything.
229 if (subMemInfoIt == memInfoMap.end() ||
230 subMemInfoIt->second.extraPorts.empty())
231 return WalkResult::advance();
232 auto &subMemInfo = subMemInfoIt->second;
233 // Find out how many memory ports we have to add.
234 auto &subExtraPorts = subMemInfo.extraPorts;
235
236 // Add the extra ports to the instance operation.
237 auto clone = inst.cloneAndInsertPorts(subExtraPorts);
238 inst.replaceAllUsesWith(
239 clone.getResults().drop_back(subExtraPorts.size()));
240 instanceGraph->replaceInstance(inst, clone);
241 inst->erase();
242 inst = clone;
243
244 // Connect each submodule port up to the parent module ports.
245 for (unsigned i = 0, e = subExtraPorts.size(); i < e; ++i) {
246 auto &[firstSubIndex, portInfo] = subExtraPorts[i];
247 // This is the index of the user port we are adding.
248 auto userIndex = i % userPorts.size();
249 auto const &sramPort = userPorts[userIndex];
250 // Construct a port name, e.g. "sram_0_user_inputs".
251 auto sramIndex = extraPorts.size() / userPorts.size();
252 auto portName =
253 StringAttr::get(context, "sram_" + Twine(sramIndex) + "_" +
254 sramPort.name.getValue());
255 auto portDirection = sramPort.direction;
256 auto portType = sramPort.type;
257 // Record the extra port.
258 extraPorts.push_back(
259 {firstPortIndex,
260 {portName, type_cast<FIRRTLType>(portType), portDirection}});
261 // If this is the DUT, then add a DontTouchAnnotation to any added ports
262 // to guarantee that it won't be removed.
263 if (instanceInfo->isEffectiveDut(moduleOp))
264 extraPorts.back().second.annotations.addDontTouch();
265 // Record the instance result for now, so that we can connect it to the
266 // parent module port after we actually add the ports.
267 values.push_back(inst.getResult(firstSubIndex + i));
268 }
269
270 // We don't want to collect the instance paths or attach inner_syms to
271 // the instance path if we aren't creating the output file.
272 if (outputFile) {
273 // We record any instance paths to memories which are rooted at the
274 // current module.
275 auto &instancePaths = memInfo.instancePaths;
276 auto ref = getInnerRefTo(inst);
277 innerRefToInstanceMap[ref] = inst;
278 // If its a mem module, this is the start of a path to the module.
279 if (isa<FMemModuleOp>(submodule))
280 instancePaths.push_back({ref});
281 // Copy any paths through the submodule to memories, adding the ref to
282 // the current instance.
283 for (const auto &subPath : subMemInfo.instancePaths) {
284 instancePaths.push_back(subPath);
285 instancePaths.back().push_back(ref);
286 }
287 }
288
289 return WalkResult::advance();
290 }
291
292 return WalkResult::advance();
293 });
294
295 if (result.wasInterrupted())
296 return failure();
297
298 // Add the extra ports to this module.
299 moduleOp.insertPorts(extraPorts);
300
301 // Get an existing invalid value or create a new one.
302 DenseMap<Type, InvalidValueOp> invalids;
303 auto getOrCreateInvalid = [&](Type type) -> InvalidValueOp {
304 auto it = invalids.find(type);
305 if (it != invalids.end())
306 return it->getSecond();
307 return invalids
308 .insert({type,
309 builder.create<InvalidValueOp>(builder.getUnknownLoc(), type)})
310 .first->getSecond();
311 };
312
313 // Connect the submodule ports to the parent module ports.
314 DenseMap<Operation *, OpBuilder::InsertPoint> instToInsertionPoint;
315 for (unsigned i = 0, e = values.size(); i < e; ++i) {
316 auto &[firstArg, port] = extraPorts[i];
317 Value modulePort = moduleOp.getArgument(firstArg + i);
318 Value instPort = values[i];
319 Operation *instOp = instPort.getDefiningOp();
320 auto insertPoint = instToInsertionPoint.find(instOp);
321 if (insertPoint == instToInsertionPoint.end())
322 builder.setInsertionPointAfter(instOp);
323 else
324 builder.restoreInsertionPoint(insertPoint->getSecond());
325 if (port.direction == Direction::In)
326 std::swap(modulePort, instPort);
327 auto connectOp =
328 builder.create<MatchingConnectOp>(port.loc, modulePort, instPort);
329 instToInsertionPoint[instOp] = builder.saveInsertionPoint();
330 // If the connect was created inside a WhenOp, then the port needs to be
331 // invalidated to make a legal circuit.
332 if (port.direction == Direction::Out &&
333 connectOp->getParentOfType<WhenOp>()) {
334 builder.setInsertionPointToStart(moduleOp.getBodyBlock());
335 builder.create<MatchingConnectOp>(port.loc, modulePort,
336 getOrCreateInvalid(port.type));
337 }
338 }
339 return success();
340}
341
342void AddSeqMemPortsPass::createOutputFile(igraph::ModuleOpInterface moduleOp) {
343 // Insert the verbatim at the bottom of the circuit.
344 auto circuit = getOperation();
345 auto builder = OpBuilder::atBlockEnd(circuit.getBodyBlock());
346
347 // Output buffer.
348 std::string buffer;
349 llvm::raw_string_ostream os(buffer);
350
351 SymbolTable &symTable = getAnalysis<SymbolTable>();
352 HierPathCache cache(circuit, symTable);
353
354 // The current parameter to the verbatim op.
355 unsigned paramIndex = 0;
356 // Parameters to the verbatim op.
357 SmallVector<Attribute> params;
358 // Small cache to reduce the number of parameters passed to the verbatim.
359 DenseMap<Attribute, unsigned> usedParams;
360
361 // Helper to add a symbol to the verbatim, hitting the cache on the way.
362 auto addSymbol = [&](Attribute ref) {
363 auto it = usedParams.find(ref);
364 unsigned index;
365 if (it != usedParams.end()) {
366 index = it->second;
367 } else {
368 index = paramIndex;
369 usedParams[ref] = paramIndex++;
370 params.push_back(ref);
371 }
372 os << "{{" << index << "}}";
373 };
374
375 // The current sram we are processing.
376 unsigned sramIndex = 0;
377 auto &instancePaths = memInfoMap[moduleOp].instancePaths;
378 auto dutSymbol = FlatSymbolRefAttr::get(moduleOp.getModuleNameAttr());
379
380 auto loc = builder.getUnknownLoc();
381 // Put the information in a verbatim operation.
382 builder.create<emit::FileOp>(loc, outputFile, [&] {
383 for (auto instancePath : instancePaths) {
384 // Note: Reverse instancepath to construct the NLA.
385 SmallVector<Attribute> path(llvm::reverse(instancePath));
386 os << sramIndex++ << " -> ";
387 addSymbol(dutSymbol);
388 os << ".";
389
390 auto nlaSymbol = cache.getRefFor(builder.getArrayAttr(path));
391 addSymbol(nlaSymbol);
392 NamedAttrList fields;
393 // There is no current client for the distinct attr, but it will be used
394 // by OM::path once the metadata is moved to OM, instead of the verbatim.
395 auto id = DistinctAttr::create(UnitAttr::get(builder.getContext()));
396 fields.append("id", id);
397 fields.append("class", builder.getStringAttr("circt.tracker"));
398 fields.append("circt.nonlocal", nlaSymbol);
399 // Now add the nonlocal annotation to the leaf instance.
400 auto *leafInstance = innerRefToInstanceMap[instancePath.front()];
401
402 AnnotationSet annos(leafInstance);
403 annos.addAnnotations(builder.getDictionaryAttr(fields));
404 annos.applyToOperation(leafInstance);
405
406 os << "\n";
407 }
408 builder.create<sv::VerbatimOp>(loc, buffer, ValueRange{},
409 builder.getArrayAttr(params));
410 });
411 anythingChanged = true;
412}
413
414void AddSeqMemPortsPass::runOnOperation() {
415 auto *context = &getContext();
416 auto circuit = getOperation();
417 instanceGraph = &getAnalysis<InstanceGraph>();
418 instanceInfo = &getAnalysis<InstanceInfo>();
419 circtNamespace = CircuitNamespace(circuit);
420 // Clear the state.
421 userPorts.clear();
422 memInfoMap.clear();
423 outputFile = {};
424 anythingChanged = false;
425
426 // Collect the annotations from the circuit.
427 if (failed(processAnnos(circuit)))
428 return signalPassFailure();
429
430 // SFC adds the ports in the opposite order they are attached, so we reverse
431 // the list here to match exactly.
432 std::reverse(userPorts.begin(), userPorts.end());
433
434 // Process the extra ports so we can attach it as metadata on to each memory.
435 SmallVector<Attribute> extraPorts;
436 auto ui32Type = IntegerType::get(context, 32, IntegerType::Unsigned);
437 for (auto &userPort : userPorts) {
438 SmallVector<NamedAttribute, 3> attrs;
439 attrs.emplace_back(StringAttr::get(context, "name"), userPort.name);
440 attrs.emplace_back(
441 StringAttr::get(context, "direction"),
442 StringAttr::get(
443 context, userPort.direction == Direction::In ? "input" : "output"));
444 attrs.emplace_back(
445 StringAttr::get(context, "width"),
446 IntegerAttr::get(
447 ui32Type,
448 type_cast<FIRRTLBaseType>(userPort.type).getBitWidthOrSentinel()));
449 extraPorts.push_back(DictionaryAttr::get(context, attrs));
450 }
451 extraPortsAttr = ArrayAttr::get(context, extraPorts);
452
453 // If there are no user ports, don't do anything.
454 if (!userPorts.empty()) {
455 // Update ports statistic.
456 numAddedPorts += userPorts.size();
457
458 // Visit the modules in post-order starting from the effective
459 // design-under-test. Skip any modules that are wholly outside the design.
460 // If any memories are partially inside and outside the design then error.
461 for (auto *node : llvm::post_order(
462 instanceGraph->lookup(instanceInfo->getEffectiveDut()))) {
463 auto op = node->getModule();
464
465 // Skip anything wholly _not_ in the design.
466 if (!instanceInfo->anyInstanceInEffectiveDesign(op))
467 continue;
468
469 // Process the module or memory.
470 if (auto moduleOp = dyn_cast<FModuleOp>(*op)) {
471 if (failed(processModule(moduleOp)))
472 return signalPassFailure();
473 } else if (auto mem = dyn_cast<FMemModuleOp>(*op)) {
474 if (failed(processMemModule(mem)))
475 return signalPassFailure();
476 }
477 }
478
479 // We handle the DUT differently than the rest of the modules.
480 auto effectiveDut = instanceInfo->getEffectiveDut();
481 if (auto *dut = dyn_cast<FModuleOp>(&effectiveDut)) {
482 // For each instance of the dut, add the instance ports, but tie the port
483 // to 0 instead of wiring them to the parent.
484 for (auto *instRec : instanceGraph->lookup(effectiveDut)->uses()) {
485 auto inst = cast<InstanceOp>(*instRec->getInstance());
486 auto &dutMemInfo = memInfoMap[*dut];
487 // Find out how many memory ports we have to add.
488 auto &subExtraPorts = dutMemInfo.extraPorts;
489 // If there are no extra ports, we don't have to do anything.
490 if (subExtraPorts.empty())
491 continue;
492
493 // Add the extra ports to the instance operation.
494 auto clone = inst.cloneAndInsertPorts(subExtraPorts);
495 inst.replaceAllUsesWith(
496 clone.getResults().drop_back(subExtraPorts.size()));
497 instanceGraph->replaceInstance(inst, clone);
498 inst->erase();
499 inst = clone;
500
501 // Tie each port to 0.
502 OpBuilder builder(context);
503 builder.setInsertionPointAfter(inst);
504 for (unsigned i = 0, e = subExtraPorts.size(); i < e; ++i) {
505 auto &[firstResult, portInfo] = subExtraPorts[i];
506 if (portInfo.direction == Direction::Out)
507 continue;
508 auto value = inst.getResult(firstResult + i);
509 auto type = value.getType();
510 auto attr = getIntZerosAttr(type);
511 auto zero = builder.create<ConstantOp>(portInfo.loc, type, attr);
512 builder.create<MatchingConnectOp>(portInfo.loc, value, zero);
513 }
514 }
515 }
516 }
517
518 // If there is an output file, create it.
519 if (outputFile)
520 createOutputFile(instanceInfo->getEffectiveDut());
521
522 if (anythingChanged)
523 markAnalysesPreserved<InstanceGraph>();
524 else
525 markAllAnalysesPreserved();
526}
527
528std::unique_ptr<mlir::Pass> circt::firrtl::createAddSeqMemPortsPass() {
529 return std::make_unique<AddSeqMemPortsPass>();
530}
static std::unique_ptr< llvm::ToolOutputFile > createOutputFile(StringRef fileName, StringRef dirname, SharedEmitterState &emitter)
This class provides a read-only projection over the MLIR attributes that represent a set of annotatio...
bool removeAnnotations(llvm::function_ref< bool(Annotation)> predicate)
Remove all annotations from this annotation set for which predicate returns true.
Annotation getAnnotation(StringRef className) const
If this annotation set has an annotation with the specified class name, return it.
This class provides a read-only projection of an annotation.
AttrClass getMember(StringAttr name) const
Return a member of the annotation.
bool isClass(Args... names) const
Return true if this annotation matches any of the specified class names.
This graph tracks modules and where they are instantiated.
constexpr const char * metadataDirectoryAttrName
hw::InnerRefAttr getInnerRefTo(const hw::InnerSymTarget &target, GetNamespaceCallback getNamespace)
Obtain an inner reference to the target (operation or port), adding an inner symbol as necessary.
constexpr const char * addSeqMemPortAnnoClass
constexpr const char * addSeqMemPortsFileAnnoClass
std::unique_ptr< mlir::Pass > createAddSeqMemPortsPass()
IntegerAttr getIntZerosAttr(Type type)
Utility for generating a constant zero attribute.
The InstanceGraph op interface, see InstanceGraphInterface.td for more details.
The namespace of a CircuitOp, generally inhabited by modules.
Definition Namespace.h:24
A cache of existing HierPathOps, mostly used to facilitate HierPathOp reuse.