CIRCT 24.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
25#include "mlir/Pass/Pass.h"
26#include "llvm/ADT/PostOrderIterator.h"
27#include "llvm/ADT/STLFunctionalExtras.h"
28#include "llvm/Support/Path.h"
29
30namespace circt {
31namespace firrtl {
32#define GEN_PASS_DEF_ADDSEQMEMPORTS
33#include "circt/Dialect/FIRRTL/Passes.h.inc"
34} // namespace firrtl
35} // namespace circt
36
37using namespace circt;
38using namespace firrtl;
39
40namespace {
41struct AddSeqMemPortsPass
42 : public circt::firrtl::impl::AddSeqMemPortsBase<AddSeqMemPortsPass> {
43 void runOnOperation() override;
44 LogicalResult processAddPortAnno(Location loc, Annotation anno);
45 LogicalResult processFileAnno(Location loc, StringRef metadataDir,
46 Annotation anno);
47 LogicalResult processAnnos(CircuitOp circuit);
48 void createOutputFile(igraph::ModuleOpInterface moduleOp);
49 LogicalResult processMemModule(FMemModuleOp mem);
50 LogicalResult processModule(FModuleOp moduleOp);
51
52 /// Get the cached namespace for a module.
53 hw::InnerSymbolNamespace &getModuleNamespace(FModuleLike moduleOp) {
54 return moduleNamespaces.try_emplace(moduleOp, moduleOp).first->second;
55 }
56
57 /// Obtain an inner reference to an operation, possibly adding an `inner_sym`
58 /// to that operation.
59 hw::InnerRefAttr getInnerRefTo(Operation *op) {
60 return ::getInnerRefTo(op,
61 [&](FModuleLike mod) -> hw::InnerSymbolNamespace & {
62 return getModuleNamespace(mod);
63 });
64 }
65
66 /// This represents the collected information of the memories in a module.
67 struct MemoryInfo {
68 /// The list of extra ports added to this module to support additional ports
69 /// on all memories underneath this module.
70 std::vector<std::pair<unsigned, PortInfo>> extraPorts;
71
72 /// This is an ordered list of instance paths to all memories underneath
73 /// this module. This will record each memory once for every port added,
74 /// which is for some reason the format of the metadata file.
75 std::vector<SmallVector<Attribute>> instancePaths;
76 };
77
78 CircuitNamespace circtNamespace;
79 /// This maps a module to information about the memories.
80 DenseMap<Operation *, MemoryInfo> memInfoMap;
81 DenseMap<Attribute, Operation *> innerRefToInstanceMap;
82
83 InstanceGraph *instanceGraph;
84 InstanceInfo *instanceInfo;
85
86 /// If the metadata output file was specified in an annotation.
87 StringAttr outputFile;
88
89 /// This is the list of every port to be added to each sequential memory.
90 SmallVector<PortInfo> userPorts;
91 /// This is an attribute holding the metadata for extra ports.
92 ArrayAttr extraPortsAttr;
93
94 /// Cached module namespaces.
95 DenseMap<Operation *, hw::InnerSymbolNamespace> moduleNamespaces;
96
97 bool anythingChanged;
98};
99} // end anonymous namespace
100
101LogicalResult AddSeqMemPortsPass::processAddPortAnno(Location loc,
102 Annotation anno) {
103 auto name = anno.getMember<StringAttr>("name");
104 if (!name)
105 return emitError(
106 loc, "AddSeqMemPortAnnotation requires field 'name' of string type");
107
108 auto input = anno.getMember<BoolAttr>("input");
109 if (!input)
110 return emitError(
111 loc, "AddSeqMemPortAnnotation requires field 'input' of boolean type");
112 auto direction = input.getValue() ? Direction::In : Direction::Out;
113
114 auto width = anno.getMember<IntegerAttr>("width");
115 if (!width)
116 return emitError(
117 loc, "AddSeqMemPortAnnotation requires field 'width' of integer type");
118 auto type = UIntType::get(&getContext(), width.getInt());
119 userPorts.push_back({name, type, direction});
120 return success();
121}
122
123LogicalResult AddSeqMemPortsPass::processFileAnno(Location loc,
124 StringRef metadataDir,
125 Annotation anno) {
126 if (outputFile)
127 return emitError(
128 loc, "circuit has two AddSeqMemPortsFileAnnotation annotations");
129
130 auto filename = anno.getMember<StringAttr>("filename");
131 if (!filename)
132 return emitError(loc,
133 "AddSeqMemPortsFileAnnotation requires field 'filename' "
134 "of string type");
135
136 SmallString<128> outputFilePath(metadataDir);
137 llvm::sys::path::append(outputFilePath, filename.getValue());
138 outputFile = StringAttr::get(&getContext(), outputFilePath);
139 return success();
140}
141
142LogicalResult AddSeqMemPortsPass::processAnnos(CircuitOp circuit) {
143 auto loc = circuit.getLoc();
144
145 // Find the metadata directory.
146 auto dirAnno = AnnotationSet(circuit).getAnnotation(metadataDirAnnoClass);
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;
158 AnnotationSet::removeAnnotations(circuit, [&](Annotation anno) {
159 if (error)
160 return false;
161 if (anno.isClass(addSeqMemPortAnnoClass)) {
162 error = failed(processAddPortAnno(loc, anno));
163 return true;
164 }
165 if (anno.isClass(addSeqMemPortsFileAnnoClass)) {
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 = cast<InstanceOp>(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<FInstanceLike>(*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}
static std::unique_ptr< Context > context
false return failure()
Parse an optional keyword or string and set instance into 'result'.
return success()
NamedAttrList attrs
bool removeAnnotations(llvm::function_ref< bool(Annotation)> predicate)
Remove all annotations from this annotation set for which predicate returns true.
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.
bool isEffectiveDut(igraph::ModuleOpInterface op)
Return true if this module is the design-under-test and the circuit has a design-under-test.
bool allInstancesInEffectiveDesign(igraph::ModuleOpInterface op)
Return true if all instances of this module are within (or transitively within) the effective design.
igraph::ModuleOpInterface getEffectiveDut()
Return the "effective" design-under-test.
bool anyInstanceInEffectiveDesign(igraph::ModuleOpInterface op)
Return true if any instance of this module is within (or transitively within) the effective design.
llvm::iterator_range< UseIterator > uses()
virtual void replaceInstance(InstanceOpInterface inst, InstanceOpInterface newInst)
Replaces an instance of a module with another instance.
InstanceGraphNode * lookup(ModuleOpInterface op)
Look up an InstanceGraphNode for a module.
BaseTy type_cast(Type type)
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.
std::map< std::string, std::set< std::string > > InstanceGraph
Iterates over the handshake::FuncOp's in the program to build an instance graph.
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:55