CIRCT 22.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.cloneWithInsertedPortsAndReplaceUses(subExtraPorts);
238 instanceGraph->replaceInstance(inst, clone);
239 inst->erase();
240 inst = clone;
241
242 // Connect each submodule port up to the parent module ports.
243 for (unsigned i = 0, e = subExtraPorts.size(); i < e; ++i) {
244 auto &[firstSubIndex, portInfo] = subExtraPorts[i];
245 // This is the index of the user port we are adding.
246 auto userIndex = i % userPorts.size();
247 auto const &sramPort = userPorts[userIndex];
248 // Construct a port name, e.g. "sram_0_user_inputs".
249 auto sramIndex = extraPorts.size() / userPorts.size();
250 auto portName =
251 StringAttr::get(context, "sram_" + Twine(sramIndex) + "_" +
252 sramPort.name.getValue());
253 auto portDirection = sramPort.direction;
254 auto portType = sramPort.type;
255 // Record the extra port.
256 extraPorts.push_back(
257 {firstPortIndex,
258 {portName, type_cast<FIRRTLType>(portType), portDirection}});
259 // If this is the DUT, then add a DontTouchAnnotation to any added ports
260 // to guarantee that it won't be removed.
261 if (instanceInfo->isEffectiveDut(moduleOp))
262 extraPorts.back().second.annotations.addDontTouch();
263 // Record the instance result for now, so that we can connect it to the
264 // parent module port after we actually add the ports.
265 values.push_back(inst.getResult(firstSubIndex + i));
266 }
267
268 // We don't want to collect the instance paths or attach inner_syms to
269 // the instance path if we aren't creating the output file.
270 if (outputFile) {
271 // We record any instance paths to memories which are rooted at the
272 // current module.
273 auto &instancePaths = memInfo.instancePaths;
274 auto ref = getInnerRefTo(inst);
275 innerRefToInstanceMap[ref] = inst;
276 // If its a mem module, this is the start of a path to the module.
277 if (isa<FMemModuleOp>(submodule))
278 instancePaths.push_back({ref});
279 // Copy any paths through the submodule to memories, adding the ref to
280 // the current instance.
281 for (const auto &subPath : subMemInfo.instancePaths) {
282 instancePaths.push_back(subPath);
283 instancePaths.back().push_back(ref);
284 }
285 }
286
287 return WalkResult::advance();
288 }
289
290 return WalkResult::advance();
291 });
292
293 if (result.wasInterrupted())
294 return failure();
295
296 // Add the extra ports to this module.
297 moduleOp.insertPorts(extraPorts);
298
299 // Get an existing invalid value or create a new one.
300 DenseMap<Type, InvalidValueOp> invalids;
301 auto getOrCreateInvalid = [&](Type type) -> InvalidValueOp {
302 auto it = invalids.find(type);
303 if (it != invalids.end())
304 return it->getSecond();
305 return invalids
306 .insert({type, InvalidValueOp::create(builder, builder.getUnknownLoc(),
307 type)})
308 .first->getSecond();
309 };
310
311 // Connect the submodule ports to the parent module ports.
312 DenseMap<Operation *, OpBuilder::InsertPoint> instToInsertionPoint;
313 for (unsigned i = 0, e = values.size(); i < e; ++i) {
314 auto &[firstArg, port] = extraPorts[i];
315 Value modulePort = moduleOp.getArgument(firstArg + i);
316 Value instPort = values[i];
317 Operation *instOp = instPort.getDefiningOp();
318 auto insertPoint = instToInsertionPoint.find(instOp);
319 if (insertPoint == instToInsertionPoint.end())
320 builder.setInsertionPointAfter(instOp);
321 else
322 builder.restoreInsertionPoint(insertPoint->getSecond());
323 if (port.direction == Direction::In)
324 std::swap(modulePort, instPort);
325 auto connectOp =
326 MatchingConnectOp::create(builder, port.loc, modulePort, instPort);
327 instToInsertionPoint[instOp] = builder.saveInsertionPoint();
328 // If the connect was created inside a WhenOp, then the port needs to be
329 // invalidated to make a legal circuit.
330 if (port.direction == Direction::Out &&
331 connectOp->getParentOfType<WhenOp>()) {
332 builder.setInsertionPointToStart(moduleOp.getBodyBlock());
333 MatchingConnectOp::create(builder, port.loc, modulePort,
334 getOrCreateInvalid(port.type));
335 }
336 }
337 return success();
338}
339
340void AddSeqMemPortsPass::createOutputFile(igraph::ModuleOpInterface moduleOp) {
341 // Insert the verbatim at the bottom of the circuit.
342 auto circuit = getOperation();
343 auto builder = OpBuilder::atBlockEnd(circuit.getBodyBlock());
344
345 // Output buffer.
346 std::string buffer;
347 llvm::raw_string_ostream os(buffer);
348
349 SymbolTable &symTable = getAnalysis<SymbolTable>();
350 HierPathCache cache(circuit, symTable);
351
352 // The current parameter to the verbatim op.
353 unsigned paramIndex = 0;
354 // Parameters to the verbatim op.
355 SmallVector<Attribute> params;
356 // Small cache to reduce the number of parameters passed to the verbatim.
357 DenseMap<Attribute, unsigned> usedParams;
358
359 // Helper to add a symbol to the verbatim, hitting the cache on the way.
360 auto addSymbol = [&](Attribute ref) {
361 auto it = usedParams.find(ref);
362 unsigned index;
363 if (it != usedParams.end()) {
364 index = it->second;
365 } else {
366 index = paramIndex;
367 usedParams[ref] = paramIndex++;
368 params.push_back(ref);
369 }
370 os << "{{" << index << "}}";
371 };
372
373 // The current sram we are processing.
374 unsigned sramIndex = 0;
375 auto &instancePaths = memInfoMap[moduleOp].instancePaths;
376 auto dutSymbol = FlatSymbolRefAttr::get(moduleOp.getModuleNameAttr());
377
378 auto loc = builder.getUnknownLoc();
379 // Put the information in a verbatim operation.
380 emit::FileOp::create(builder, loc, outputFile, [&] {
381 for (auto instancePath : instancePaths) {
382 // Note: Reverse instancepath to construct the NLA.
383 SmallVector<Attribute> path(llvm::reverse(instancePath));
384 os << sramIndex++ << " -> ";
385 addSymbol(dutSymbol);
386 os << ".";
387
388 auto nlaSymbol = cache.getRefFor(builder.getArrayAttr(path));
389 addSymbol(nlaSymbol);
390 NamedAttrList fields;
391 // There is no current client for the distinct attr, but it will be used
392 // by OM::path once the metadata is moved to OM, instead of the verbatim.
393 auto id = DistinctAttr::create(UnitAttr::get(builder.getContext()));
394 fields.append("id", id);
395 fields.append("class", builder.getStringAttr("circt.tracker"));
396 fields.append("circt.nonlocal", nlaSymbol);
397 // Now add the nonlocal annotation to the leaf instance.
398 auto *leafInstance = innerRefToInstanceMap[instancePath.front()];
399
400 AnnotationSet annos(leafInstance);
401 annos.addAnnotations(builder.getDictionaryAttr(fields));
402 annos.applyToOperation(leafInstance);
403
404 os << "\n";
405 }
406 sv::VerbatimOp::create(builder, loc, buffer, ValueRange{},
407 builder.getArrayAttr(params));
408 });
409 anythingChanged = true;
410}
411
412void AddSeqMemPortsPass::runOnOperation() {
413 auto *context = &getContext();
414 auto circuit = getOperation();
415 instanceGraph = &getAnalysis<InstanceGraph>();
416 instanceInfo = &getAnalysis<InstanceInfo>();
417 circtNamespace = CircuitNamespace(circuit);
418 // Clear the state.
419 userPorts.clear();
420 memInfoMap.clear();
421 outputFile = {};
422 anythingChanged = false;
423
424 // Collect the annotations from the circuit.
425 if (failed(processAnnos(circuit)))
426 return signalPassFailure();
427
428 // SFC adds the ports in the opposite order they are attached, so we reverse
429 // the list here to match exactly.
430 std::reverse(userPorts.begin(), userPorts.end());
431
432 // Process the extra ports so we can attach it as metadata on to each memory.
433 SmallVector<Attribute> extraPorts;
434 auto ui32Type = IntegerType::get(context, 32, IntegerType::Unsigned);
435 for (auto &userPort : userPorts) {
436 SmallVector<NamedAttribute, 3> attrs;
437 attrs.emplace_back(StringAttr::get(context, "name"), userPort.name);
438 attrs.emplace_back(
439 StringAttr::get(context, "direction"),
440 StringAttr::get(
441 context, userPort.direction == Direction::In ? "input" : "output"));
442 attrs.emplace_back(
443 StringAttr::get(context, "width"),
444 IntegerAttr::get(
445 ui32Type,
446 type_cast<FIRRTLBaseType>(userPort.type).getBitWidthOrSentinel()));
447 extraPorts.push_back(DictionaryAttr::get(context, attrs));
448 }
449 extraPortsAttr = ArrayAttr::get(context, extraPorts);
450
451 // If there are no user ports, don't do anything.
452 if (!userPorts.empty()) {
453 // Update ports statistic.
454 numAddedPorts += userPorts.size();
455
456 // Visit the modules in post-order starting from the effective
457 // design-under-test. Skip any modules that are wholly outside the design.
458 // If any memories are partially inside and outside the design then error.
459 for (auto *node : llvm::post_order(
460 instanceGraph->lookup(instanceInfo->getEffectiveDut()))) {
461 auto op = node->getModule();
462
463 // Skip anything wholly _not_ in the design.
464 if (!instanceInfo->anyInstanceInEffectiveDesign(op))
465 continue;
466
467 // Process the module or memory.
468 if (auto moduleOp = dyn_cast<FModuleOp>(*op)) {
469 if (failed(processModule(moduleOp)))
470 return signalPassFailure();
471 } else if (auto mem = dyn_cast<FMemModuleOp>(*op)) {
472 if (failed(processMemModule(mem)))
473 return signalPassFailure();
474 }
475 }
476
477 // We handle the DUT differently than the rest of the modules.
478 auto effectiveDut = instanceInfo->getEffectiveDut();
479 if (auto *dut = dyn_cast<FModuleOp>(&effectiveDut)) {
480 // For each instance of the dut, add the instance ports, but tie the port
481 // to 0 instead of wiring them to the parent.
482 for (auto *instRec : instanceGraph->lookup(effectiveDut)->uses()) {
483 auto inst = cast<InstanceOp>(*instRec->getInstance());
484 auto &dutMemInfo = memInfoMap[*dut];
485 // Find out how many memory ports we have to add.
486 auto &subExtraPorts = dutMemInfo.extraPorts;
487 // If there are no extra ports, we don't have to do anything.
488 if (subExtraPorts.empty())
489 continue;
490
491 // Add the extra ports to the instance operation.
492 auto clone = inst.cloneWithInsertedPortsAndReplaceUses(subExtraPorts);
493 instanceGraph->replaceInstance(inst, clone);
494 inst->erase();
495 inst = clone;
496
497 // Tie each port to 0.
498 OpBuilder builder(context);
499 builder.setInsertionPointAfter(inst);
500 for (unsigned i = 0, e = subExtraPorts.size(); i < e; ++i) {
501 auto &[firstResult, portInfo] = subExtraPorts[i];
502 if (portInfo.direction == Direction::Out)
503 continue;
504 auto value = inst.getResult(firstResult + i);
505 auto type = value.getType();
506 auto attr = getIntZerosAttr(type);
507 auto zero = ConstantOp::create(builder, portInfo.loc, type, attr);
508 MatchingConnectOp::create(builder, portInfo.loc, value, zero);
509 }
510 }
511 }
512 }
513
514 // If there is an output file, create it.
515 if (outputFile)
516 createOutputFile(instanceInfo->getEffectiveDut());
517
518 if (anythingChanged)
519 markAnalysesPreserved<InstanceGraph>();
520 else
521 markAllAnalysesPreserved();
522}
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
IntegerAttr getIntZerosAttr(Type type)
Utility for generating a constant zero attribute.
void error(Twine message)
Definition LSPUtils.cpp:16
The InstanceGraph op interface, see InstanceGraphInterface.td for more details.
std::unique_ptr< llvm::ToolOutputFile > createOutputFile(StringRef filename, StringRef dirname, function_ref< InFlightDiagnostic()> emitError)
Creates an output file with the given filename in the specified directory.
Definition Path.cpp:37
The namespace of a CircuitOp, generally inhabited by modules.
Definition Namespace.h:24
A cache of existing HierPathOps, mostly used to facilitate HierPathOp reuse.