CIRCT 23.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 = AnnotationSet(circuit).getAnnotation(metadataDirAnnoClass);
146 StringRef metadataDir = "metadata";
147 if (dirAnno) {
148 auto dir = dirAnno.getMember<StringAttr>("dirname");
149 if (!dir)
150 return emitError(loc, "MetadataDirAnnotation requires field 'dirname' of "
151 "string type");
152 metadataDir = dir.getValue();
153 }
154
155 // Remove the annotations we care about.
156 bool error = false;
158 if (error)
159 return false;
160 if (anno.isClass(addSeqMemPortAnnoClass)) {
161 error = failed(processAddPortAnno(loc, anno));
162 return true;
163 }
164 if (anno.isClass(addSeqMemPortsFileAnnoClass)) {
165 error = failed(processFileAnno(loc, metadataDir, anno));
166 return true;
167 }
168 return false;
169 });
170 return failure(error);
171}
172
173LogicalResult AddSeqMemPortsPass::processMemModule(FMemModuleOp mem) {
174 // Error if all instances are not under the effective DUT.
175 if (!instanceInfo->allInstancesInEffectiveDesign(mem)) {
176 auto diag = mem->emitOpError()
177 << "cannot have ports added to it because it is instantiated "
178 "both under and not under the design-under-test (DUT)";
179 for (auto *instNode : instanceGraph->lookup(mem)->uses()) {
180 auto instanceOp = instNode->getInstance();
181 if (auto layerBlockOp = instanceOp->getParentOfType<LayerBlockOp>()) {
182 diag.attachNote(instanceOp.getLoc())
183 << "this instance is under a layer block";
184 diag.attachNote(layerBlockOp.getLoc())
185 << "the innermost layer block is here";
186 continue;
187 }
188 if (!instanceInfo->allInstancesInEffectiveDesign(
189 instNode->getParent()->getModule())) {
190 diag.attachNote(instanceOp.getLoc())
191 << "this instance is inside a module that is instantiated outside "
192 "the design";
193 }
194 }
195 return failure();
196 }
197
198 // We have to add the user ports to every mem module.
199 size_t portIndex = mem.getNumPorts();
200 auto &memInfo = memInfoMap[mem];
201 auto &extraPorts = memInfo.extraPorts;
202 for (auto const &p : userPorts)
203 extraPorts.emplace_back(portIndex, p);
204 mem.insertPorts(extraPorts);
205 // Attach the extraPorts metadata.
206 mem.setExtraPortsAttr(extraPortsAttr);
207 return success();
208}
209
210LogicalResult AddSeqMemPortsPass::processModule(FModuleOp moduleOp) {
211 auto *context = &getContext();
212 auto builder = OpBuilder(moduleOp.getContext());
213 auto &memInfo = memInfoMap[moduleOp];
214 auto &extraPorts = memInfo.extraPorts;
215 // List of ports added to submodules which must be connected to this module's
216 // ports.
217 SmallVector<Value> values;
218
219 // The base index to use when adding ports to the current module.
220 unsigned firstPortIndex = moduleOp.getNumPorts();
221
222 auto result = moduleOp.walk([&](Operation *op) {
223 if (auto inst = dyn_cast<InstanceOp>(op)) {
224 auto submodule = inst.getReferencedModule(*instanceGraph);
225
226 auto subMemInfoIt = memInfoMap.find(submodule);
227 // If there are no extra ports, we don't have to do anything.
228 if (subMemInfoIt == memInfoMap.end() ||
229 subMemInfoIt->second.extraPorts.empty())
230 return WalkResult::advance();
231 auto &subMemInfo = subMemInfoIt->second;
232 // Find out how many memory ports we have to add.
233 auto &subExtraPorts = subMemInfo.extraPorts;
234
235 // Add the extra ports to the instance operation.
236 auto clone = inst.cloneWithInsertedPortsAndReplaceUses(subExtraPorts);
237 instanceGraph->replaceInstance(inst, clone);
238 inst->erase();
239 inst = clone;
240
241 // Connect each submodule port up to the parent module ports.
242 for (unsigned i = 0, e = subExtraPorts.size(); i < e; ++i) {
243 auto &[firstSubIndex, portInfo] = subExtraPorts[i];
244 // This is the index of the user port we are adding.
245 auto userIndex = i % userPorts.size();
246 auto const &sramPort = userPorts[userIndex];
247 // Construct a port name, e.g. "sram_0_user_inputs".
248 auto sramIndex = extraPorts.size() / userPorts.size();
249 auto portName =
250 StringAttr::get(context, "sram_" + Twine(sramIndex) + "_" +
251 sramPort.name.getValue());
252 auto portDirection = sramPort.direction;
253 auto portType = sramPort.type;
254 // Record the extra port.
255 extraPorts.push_back(
256 {firstPortIndex,
257 {portName, type_cast<FIRRTLType>(portType), portDirection}});
258 // If this is the DUT, then add a DontTouchAnnotation to any added ports
259 // to guarantee that it won't be removed.
260 if (instanceInfo->isEffectiveDut(moduleOp))
261 extraPorts.back().second.annotations.addDontTouch();
262 // Record the instance result for now, so that we can connect it to the
263 // parent module port after we actually add the ports.
264 values.push_back(inst.getResult(firstSubIndex + i));
265 }
266
267 // We don't want to collect the instance paths or attach inner_syms to
268 // the instance path if we aren't creating the output file.
269 if (outputFile) {
270 // We record any instance paths to memories which are rooted at the
271 // current module.
272 auto &instancePaths = memInfo.instancePaths;
273 auto ref = getInnerRefTo(inst);
274 innerRefToInstanceMap[ref] = inst;
275 // If its a mem module, this is the start of a path to the module.
276 if (isa<FMemModuleOp>(submodule))
277 instancePaths.push_back({ref});
278 // Copy any paths through the submodule to memories, adding the ref to
279 // the current instance.
280 for (const auto &subPath : subMemInfo.instancePaths) {
281 instancePaths.push_back(subPath);
282 instancePaths.back().push_back(ref);
283 }
284 }
285
286 return WalkResult::advance();
287 }
288
289 return WalkResult::advance();
290 });
291
292 if (result.wasInterrupted())
293 return failure();
294
295 // Add the extra ports to this module.
296 moduleOp.insertPorts(extraPorts);
297
298 // Get an existing invalid value or create a new one.
299 DenseMap<Type, InvalidValueOp> invalids;
300 auto getOrCreateInvalid = [&](Type type) -> InvalidValueOp {
301 auto it = invalids.find(type);
302 if (it != invalids.end())
303 return it->getSecond();
304 return invalids
305 .insert({type, InvalidValueOp::create(builder, builder.getUnknownLoc(),
306 type)})
307 .first->getSecond();
308 };
309
310 // Connect the submodule ports to the parent module ports.
311 DenseMap<Operation *, OpBuilder::InsertPoint> instToInsertionPoint;
312 for (unsigned i = 0, e = values.size(); i < e; ++i) {
313 auto &[firstArg, port] = extraPorts[i];
314 Value modulePort = moduleOp.getArgument(firstArg + i);
315 Value instPort = values[i];
316 Operation *instOp = instPort.getDefiningOp();
317 auto insertPoint = instToInsertionPoint.find(instOp);
318 if (insertPoint == instToInsertionPoint.end())
319 builder.setInsertionPointAfter(instOp);
320 else
321 builder.restoreInsertionPoint(insertPoint->getSecond());
322 if (port.direction == Direction::In)
323 std::swap(modulePort, instPort);
324 auto connectOp =
325 MatchingConnectOp::create(builder, port.loc, modulePort, instPort);
326 instToInsertionPoint[instOp] = builder.saveInsertionPoint();
327 // If the connect was created inside a WhenOp, then the port needs to be
328 // invalidated to make a legal circuit.
329 if (port.direction == Direction::Out &&
330 connectOp->getParentOfType<WhenOp>()) {
331 builder.setInsertionPointToStart(moduleOp.getBodyBlock());
332 MatchingConnectOp::create(builder, port.loc, modulePort,
333 getOrCreateInvalid(port.type));
334 }
335 }
336 return success();
337}
338
339void AddSeqMemPortsPass::createOutputFile(igraph::ModuleOpInterface moduleOp) {
340 // Insert the verbatim at the bottom of the circuit.
341 auto circuit = getOperation();
342 auto builder = OpBuilder::atBlockEnd(circuit.getBodyBlock());
343
344 // Output buffer.
345 std::string buffer;
346 llvm::raw_string_ostream os(buffer);
347
348 SymbolTable &symTable = getAnalysis<SymbolTable>();
349 HierPathCache cache(circuit, symTable);
350
351 // The current parameter to the verbatim op.
352 unsigned paramIndex = 0;
353 // Parameters to the verbatim op.
354 SmallVector<Attribute> params;
355 // Small cache to reduce the number of parameters passed to the verbatim.
356 DenseMap<Attribute, unsigned> usedParams;
357
358 // Helper to add a symbol to the verbatim, hitting the cache on the way.
359 auto addSymbol = [&](Attribute ref) {
360 auto it = usedParams.find(ref);
361 unsigned index;
362 if (it != usedParams.end()) {
363 index = it->second;
364 } else {
365 index = paramIndex;
366 usedParams[ref] = paramIndex++;
367 params.push_back(ref);
368 }
369 os << "{{" << index << "}}";
370 };
371
372 // The current sram we are processing.
373 unsigned sramIndex = 0;
374 auto &instancePaths = memInfoMap[moduleOp].instancePaths;
375 auto dutSymbol = FlatSymbolRefAttr::get(moduleOp.getModuleNameAttr());
376
377 auto loc = builder.getUnknownLoc();
378 // Put the information in a verbatim operation.
379 emit::FileOp::create(builder, loc, outputFile, [&] {
380 for (auto instancePath : instancePaths) {
381 // Note: Reverse instancepath to construct the NLA.
382 SmallVector<Attribute> path(llvm::reverse(instancePath));
383 os << sramIndex++ << " -> ";
384 addSymbol(dutSymbol);
385 os << ".";
386
387 auto nlaSymbol = cache.getRefFor(builder.getArrayAttr(path));
388 addSymbol(nlaSymbol);
389 NamedAttrList fields;
390 // There is no current client for the distinct attr, but it will be used
391 // by OM::path once the metadata is moved to OM, instead of the verbatim.
392 auto id = DistinctAttr::create(UnitAttr::get(builder.getContext()));
393 fields.append("id", id);
394 fields.append("class", builder.getStringAttr("circt.tracker"));
395 fields.append("circt.nonlocal", nlaSymbol);
396 // Now add the nonlocal annotation to the leaf instance.
397 auto *leafInstance = innerRefToInstanceMap[instancePath.front()];
398
399 AnnotationSet annos(leafInstance);
400 annos.addAnnotations(builder.getDictionaryAttr(fields));
401 annos.applyToOperation(leafInstance);
402
403 os << "\n";
404 }
405 sv::VerbatimOp::create(builder, loc, buffer, ValueRange{},
406 builder.getArrayAttr(params));
407 });
408 anythingChanged = true;
409}
410
411void AddSeqMemPortsPass::runOnOperation() {
412 auto *context = &getContext();
413 auto circuit = getOperation();
414 instanceGraph = &getAnalysis<InstanceGraph>();
415 instanceInfo = &getAnalysis<InstanceInfo>();
416 circtNamespace = CircuitNamespace(circuit);
417 // Clear the state.
418 userPorts.clear();
419 memInfoMap.clear();
420 outputFile = {};
421 anythingChanged = false;
422
423 // Collect the annotations from the circuit.
424 if (failed(processAnnos(circuit)))
425 return signalPassFailure();
426
427 // SFC adds the ports in the opposite order they are attached, so we reverse
428 // the list here to match exactly.
429 std::reverse(userPorts.begin(), userPorts.end());
430
431 // Process the extra ports so we can attach it as metadata on to each memory.
432 SmallVector<Attribute> extraPorts;
433 auto ui32Type = IntegerType::get(context, 32, IntegerType::Unsigned);
434 for (auto &userPort : userPorts) {
435 SmallVector<NamedAttribute, 3> attrs;
436 attrs.emplace_back(StringAttr::get(context, "name"), userPort.name);
437 attrs.emplace_back(
438 StringAttr::get(context, "direction"),
439 StringAttr::get(
440 context, userPort.direction == Direction::In ? "input" : "output"));
441 attrs.emplace_back(
442 StringAttr::get(context, "width"),
443 IntegerAttr::get(
444 ui32Type,
445 type_cast<FIRRTLBaseType>(userPort.type).getBitWidthOrSentinel()));
446 extraPorts.push_back(DictionaryAttr::get(context, attrs));
447 }
448 extraPortsAttr = ArrayAttr::get(context, extraPorts);
449
450 // If there are no user ports, don't do anything.
451 if (!userPorts.empty()) {
452 // Update ports statistic.
453 numAddedPorts += userPorts.size();
454
455 // Visit the modules in post-order starting from the effective
456 // design-under-test. Skip any modules that are wholly outside the design.
457 // If any memories are partially inside and outside the design then error.
458 for (auto *node : llvm::post_order(
459 instanceGraph->lookup(instanceInfo->getEffectiveDut()))) {
460 auto op = node->getModule();
461
462 // Skip anything wholly _not_ in the design.
463 if (!instanceInfo->anyInstanceInEffectiveDesign(op))
464 continue;
465
466 // Process the module or memory.
467 if (auto moduleOp = dyn_cast<FModuleOp>(*op)) {
468 if (failed(processModule(moduleOp)))
469 return signalPassFailure();
470 } else if (auto mem = dyn_cast<FMemModuleOp>(*op)) {
471 if (failed(processMemModule(mem)))
472 return signalPassFailure();
473 }
474 }
475
476 // We handle the DUT differently than the rest of the modules.
477 auto effectiveDut = instanceInfo->getEffectiveDut();
478 if (auto *dut = dyn_cast<FModuleOp>(&effectiveDut)) {
479 // For each instance of the dut, add the instance ports, but tie the port
480 // to 0 instead of wiring them to the parent.
481 for (auto *instRec : instanceGraph->lookup(effectiveDut)->uses()) {
482 auto inst = cast<InstanceOp>(*instRec->getInstance());
483 auto &dutMemInfo = memInfoMap[*dut];
484 // Find out how many memory ports we have to add.
485 auto &subExtraPorts = dutMemInfo.extraPorts;
486 // If there are no extra ports, we don't have to do anything.
487 if (subExtraPorts.empty())
488 continue;
489
490 // Add the extra ports to the instance operation.
491 auto clone = inst.cloneWithInsertedPortsAndReplaceUses(subExtraPorts);
492 instanceGraph->replaceInstance(inst, clone);
493 inst->erase();
494 inst = clone;
495
496 // Tie each port to 0.
497 OpBuilder builder(context);
498 builder.setInsertionPointAfter(inst);
499 for (unsigned i = 0, e = subExtraPorts.size(); i < e; ++i) {
500 auto &[firstResult, portInfo] = subExtraPorts[i];
501 if (portInfo.direction == Direction::Out)
502 continue;
503 auto value = inst.getResult(firstResult + i);
504 auto type = value.getType();
505 auto attr = getIntZerosAttr(type);
506 auto zero = ConstantOp::create(builder, portInfo.loc, type, attr);
507 MatchingConnectOp::create(builder, portInfo.loc, value, zero);
508 }
509 }
510 }
511 }
512
513 // If there is an output file, create it.
514 if (outputFile)
515 createOutputFile(instanceInfo->getEffectiveDut());
516
517 if (anythingChanged)
518 markAnalysesPreserved<InstanceGraph>();
519 else
520 markAllAnalysesPreserved();
521}
static std::unique_ptr< Context > context
static LogicalResult processModule(const DomainInfo &info, TermAllocator &allocator, DomainTable &table, const ModuleUpdateTable &updateTable, FModuleOp moduleOp)
Populate the domain table by processing the moduleOp.
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.
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.
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.