CIRCT 23.0.0git
Loading...
Searching...
No Matches
LowerMemory.cpp
Go to the documentation of this file.
1//===- LowerMemory.cpp - Lower 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 LowerMemories pass.
9//
10//===----------------------------------------------------------------------===//
11
20#include "mlir/IR/Dominance.h"
21#include "mlir/Pass/Pass.h"
22#include "llvm/ADT/DepthFirstIterator.h"
23#include "llvm/ADT/STLExtras.h"
24#include "llvm/ADT/STLFunctionalExtras.h"
25#include "llvm/ADT/SmallPtrSet.h"
26#include "llvm/Support/Parallel.h"
27#include <optional>
28#include <set>
29
30namespace circt {
31namespace firrtl {
32#define GEN_PASS_DEF_LOWERMEMORY
33#include "circt/Dialect/FIRRTL/Passes.h.inc"
34} // namespace firrtl
35} // namespace circt
36
37using namespace circt;
38using namespace firrtl;
39
40// Extract all the relevant attributes from the MemOp and return the FirMemory.
42 size_t numReadPorts = 0;
43 size_t numWritePorts = 0;
44 size_t numReadWritePorts = 0;
46 SmallVector<int32_t> writeClockIDs;
47
48 for (size_t i = 0, e = op.getNumResults(); i != e; ++i) {
49 auto portKind = op.getPortKind(i);
50 if (portKind == MemOp::PortKind::Read)
51 ++numReadPorts;
52 else if (portKind == MemOp::PortKind::Write) {
53 for (auto *a : op.getResult(i).getUsers()) {
54 auto subfield = dyn_cast<SubfieldOp>(a);
55 if (!subfield || subfield.getFieldIndex() != 2)
56 continue;
57 auto clockPort = a->getResult(0);
58 for (auto *b : clockPort.getUsers()) {
59 if (auto connect = dyn_cast<FConnectLike>(b)) {
60 if (connect.getDest() == clockPort) {
61 auto result =
62 clockToLeader.insert({connect.getSrc(), numWritePorts});
63 if (result.second) {
64 writeClockIDs.push_back(numWritePorts);
65 } else {
66 writeClockIDs.push_back(result.first->second);
67 }
68 }
69 }
70 }
71 break;
72 }
73 ++numWritePorts;
74 } else
75 ++numReadWritePorts;
76 }
77
78 auto width = op.getDataType().getBitWidthOrSentinel();
79 if (width <= 0) {
80 op.emitError("'firrtl.mem' should have simple type and known width");
81 width = 0;
82 }
83 return {numReadPorts,
84 numWritePorts,
85 numReadWritePorts,
86 (size_t)width,
87 op.getDepth(),
88 op.getReadLatency(),
89 op.getWriteLatency(),
90 op.getMaskBits(),
91 *seq::symbolizeRUW(unsigned(op.getRuw())),
92 seq::WUW::PortOrder,
93 writeClockIDs,
94 op.getNameAttr(),
95 op.getMaskBits() > 1,
96 op.getInitAttr(),
97 op.getPrefixAttr(),
98 op.getLoc()};
99}
100
101namespace {
102struct LowerMemoryPass
103 : public circt::firrtl::impl::LowerMemoryBase<LowerMemoryPass> {
104
105 /// Get the cached namespace for a module.
106 hw::InnerSymbolNamespace &getModuleNamespace(FModuleLike moduleOp) {
107 return moduleNamespaces.try_emplace(moduleOp, moduleOp).first->second;
108 }
109
110 SmallVector<PortInfo> getMemoryModulePorts(const FirMemory &mem);
111 FMemModuleOp emitMemoryModule(MemOp op, const FirMemory &summary,
112 const SmallVectorImpl<PortInfo> &ports);
113 FMemModuleOp getOrCreateMemModule(MemOp op, const FirMemory &summary,
114 const SmallVectorImpl<PortInfo> &ports);
115 FModuleOp createWrapperModule(MemOp op, const FirMemory &summary);
116 InstanceOp emitMemoryInstance(MemOp op, FModuleOp moduleOp,
117 const FirMemory &summary);
118 void lowerMemory(MemOp mem, const FirMemory &summary);
119 LogicalResult runOnModule(FModuleOp moduleOp);
120 void runOnOperation() override;
121
122 /// Cached module namespaces.
123 DenseMap<Operation *, hw::InnerSymbolNamespace> moduleNamespaces;
124 CircuitNamespace circuitNamespace;
125 SymbolTable *symbolTable;
126
127 /// The set of all memories seen so far. This is used to "deduplicate"
128 /// memories by emitting modules one module for equivalent memories.
129 std::map<FirMemory, FMemModuleOp> memories;
130
131 /// A sequence of operations that should be erased later.
132 SetVector<Operation *> operationsToErase;
133};
134} // end anonymous namespace
135
136SmallVector<PortInfo>
137LowerMemoryPass::getMemoryModulePorts(const FirMemory &mem) {
138 auto *context = &getContext();
139
140 // We don't need a single bit mask, it can be combined with enable. Create
141 // an unmasked memory if maskBits = 1.
142 FIRRTLType u1Type = UIntType::get(context, 1);
143 FIRRTLType dataType = UIntType::get(context, mem.dataWidth);
144 FIRRTLType maskType = UIntType::get(context, mem.maskBits);
145 FIRRTLType addrType =
146 UIntType::get(context, std::max(1U, llvm::Log2_64_Ceil(mem.depth)));
147 FIRRTLType clockType = ClockType::get(context);
148 Location loc = UnknownLoc::get(context);
149 AnnotationSet annotations = AnnotationSet(context);
150
151 SmallVector<PortInfo> ports;
152 auto addPort = [&](const Twine &name, FIRRTLType type, Direction direction) {
153 auto nameAttr = StringAttr::get(context, name);
154 ports.push_back(
155 {nameAttr, type, direction, hw::InnerSymAttr{}, loc, annotations, {}});
156 };
157
158 auto makePortCommon = [&](StringRef prefix, size_t idx, FIRRTLType addrType) {
159 addPort(prefix + Twine(idx) + "_addr", addrType, Direction::In);
160 addPort(prefix + Twine(idx) + "_en", u1Type, Direction::In);
161 addPort(prefix + Twine(idx) + "_clk", clockType, Direction::In);
162 };
163
164 for (size_t i = 0, e = mem.numReadPorts; i != e; ++i) {
165 makePortCommon("R", i, addrType);
166 addPort("R" + Twine(i) + "_data", dataType, Direction::Out);
167 }
168 for (size_t i = 0, e = mem.numReadWritePorts; i != e; ++i) {
169 makePortCommon("RW", i, addrType);
170 addPort("RW" + Twine(i) + "_wmode", u1Type, Direction::In);
171 addPort("RW" + Twine(i) + "_wdata", dataType, Direction::In);
172 addPort("RW" + Twine(i) + "_rdata", dataType, Direction::Out);
173 // Ignore mask port, if maskBits =1
174 if (mem.isMasked)
175 addPort("RW" + Twine(i) + "_wmask", maskType, Direction::In);
176 }
177
178 for (size_t i = 0, e = mem.numWritePorts; i != e; ++i) {
179 makePortCommon("W", i, addrType);
180 addPort("W" + Twine(i) + "_data", dataType, Direction::In);
181 // Ignore mask port, if maskBits =1
182 if (mem.isMasked)
183 addPort("W" + Twine(i) + "_mask", maskType, Direction::In);
184 }
185
186 return ports;
187}
188
189FMemModuleOp
190LowerMemoryPass::emitMemoryModule(MemOp op, const FirMemory &mem,
191 const SmallVectorImpl<PortInfo> &ports) {
192 // Get a non-colliding name for the memory module, and update the summary.
193 StringRef prefix = "";
194 if (mem.prefix)
195 prefix = mem.prefix.getValue();
196 auto newName =
197 circuitNamespace.newName(prefix + mem.modName.getValue(), "ext");
198 auto moduleName = StringAttr::get(&getContext(), newName);
199
200 // Insert the memory module just above the current module.
201 OpBuilder b(op->getParentOfType<FModuleOp>());
202 ++numCreatedMemModules;
203 auto moduleOp = FMemModuleOp::create(
204 b, mem.loc, moduleName, ports, mem.numReadPorts, mem.numWritePorts,
206 mem.writeLatency, mem.depth,
207 *symbolizeRUWBehavior(static_cast<uint32_t>(mem.readUnderWrite)));
208 SymbolTable::setSymbolVisibility(moduleOp, SymbolTable::Visibility::Private);
209 return moduleOp;
210}
211
212FMemModuleOp
213LowerMemoryPass::getOrCreateMemModule(MemOp op, const FirMemory &summary,
214 const SmallVectorImpl<PortInfo> &ports) {
215 // Try to find a matching memory blackbox that we already created.
216 auto it = memories.find(summary);
217 if (it != memories.end())
218 return it->second;
219
220 // Create a new module for this memory. This can update the name recorded in
221 // the memory's summary.
222 auto moduleOp = emitMemoryModule(op, summary, ports);
223
224 // Record the memory module so it can be reused for equivalent memories.
225 memories[summary] = moduleOp;
226
227 return moduleOp;
228}
229
230void LowerMemoryPass::lowerMemory(MemOp mem, const FirMemory &summary) {
231 auto *context = &getContext();
232 auto ports = getMemoryModulePorts(summary);
233
234 // Get a non-colliding name for the memory module, and update the summary.
235 StringRef prefix = "";
236 if (summary.prefix)
237 prefix = summary.prefix.getValue();
238 auto newName = circuitNamespace.newName(prefix + mem.getName());
239
240 auto wrapperName = StringAttr::get(&getContext(), newName);
241
242 // Create the wrapper module, inserting it just before the current module.
243 OpBuilder b(mem->getParentOfType<FModuleOp>());
244 auto wrapper = FModuleOp::create(
245 b, mem->getLoc(), wrapperName,
246 ConventionAttr::get(context, Convention::Internal), ports);
247 SymbolTable::setSymbolVisibility(wrapper, SymbolTable::Visibility::Private);
248
249 // Create an instance of the external memory module. The instance has the
250 // same name as the target module.
251 auto memModule = getOrCreateMemModule(mem, summary, ports);
252 b.setInsertionPointToStart(wrapper.getBodyBlock());
253 auto memInst = InstanceOp::create(
254 b, mem->getLoc(), memModule, (mem.getName() + "_ext").str(),
255 mem.getNameKind(), mem.getAnnotations().getValue());
256
257 // Wire all the ports together.
258 for (auto [dst, src] : llvm::zip(wrapper.getBodyBlock()->getArguments(),
259 memInst.getResults())) {
260 if (wrapper.getPortDirection(dst.getArgNumber()) == Direction::Out)
261 MatchingConnectOp::create(b, mem->getLoc(), dst, src);
262 else
263 MatchingConnectOp::create(b, mem->getLoc(), src, dst);
264 }
265
266 // Create an instance of the wrapper memory module, which will replace the
267 // original mem op.
268 auto inst = emitMemoryInstance(mem, wrapper, summary);
269
270 // We fixup the annotations here. We will be copying all annotations on to the
271 // module op, so we have to fix up the NLA to have the module as the leaf
272 // element.
273
274 auto leafSym = memModule.getModuleNameAttr();
275 auto leafAttr = FlatSymbolRefAttr::get(wrapper.getModuleNameAttr());
276
277 // NLAs that we have already processed.
279 auto nonlocalAttr = StringAttr::get(context, "circt.nonlocal");
280 bool nlaUpdated = false;
281 SmallVector<Annotation> newMemModAnnos;
282 OpBuilder nlaBuilder(context);
283
284 AnnotationSet::removeAnnotations(memInst, [&](Annotation anno) -> bool {
285 // We're only looking for non-local annotations.
286 auto nlaSym = anno.getMember<FlatSymbolRefAttr>(nonlocalAttr);
287 if (!nlaSym)
288 return false;
289 // If we have already seen this NLA, don't re-process it.
290 auto newNLAIter = processedNLAs.find(nlaSym.getAttr());
291 StringAttr newNLAName;
292 if (newNLAIter == processedNLAs.end()) {
293
294 // Update the NLA path to have the additional wrapper module.
295 auto nla =
296 dyn_cast<hw::HierPathOp>(symbolTable->lookup(nlaSym.getAttr()));
297 auto namepath = nla.getNamepath().getValue();
298 SmallVector<Attribute> newNamepath(namepath.begin(), namepath.end());
299 if (!nla.isComponent())
300 newNamepath.back() =
301 getInnerRefTo(inst, [&](auto mod) -> hw::InnerSymbolNamespace & {
302 return getModuleNamespace(mod);
303 });
304 newNamepath.push_back(leafAttr);
305
306 nlaBuilder.setInsertionPointAfter(nla);
307 auto newNLA = cast<hw::HierPathOp>(nlaBuilder.clone(*nla));
308 newNLA.setSymNameAttr(StringAttr::get(
309 context, circuitNamespace.newName(nla.getNameAttr().getValue())));
310 newNLA.setNamepathAttr(ArrayAttr::get(context, newNamepath));
311 newNLAName = newNLA.getNameAttr();
312 processedNLAs[nlaSym.getAttr()] = newNLAName;
313 } else
314 newNLAName = newNLAIter->getSecond();
315 anno.setMember("circt.nonlocal", FlatSymbolRefAttr::get(newNLAName));
316 nlaUpdated = true;
317 newMemModAnnos.push_back(anno);
318 return true;
319 });
320 if (nlaUpdated) {
321 memInst.setInnerSymAttr(hw::InnerSymAttr::get(leafSym));
322 AnnotationSet newAnnos(memInst);
323 newAnnos.addAnnotations(newMemModAnnos);
324 newAnnos.applyToOperation(memInst);
325 }
326 operationsToErase.insert(mem);
327 ++numLoweredMems;
328}
329
330static SmallVector<SubfieldOp> getAllFieldAccesses(Value structValue,
331 StringRef field) {
332 SmallVector<SubfieldOp> accesses;
333 for (auto *op : structValue.getUsers()) {
334 assert(isa<SubfieldOp>(op));
335 auto fieldAccess = cast<SubfieldOp>(op);
336 auto elemIndex =
337 fieldAccess.getInput().getType().base().getElementIndex(field);
338 if (elemIndex && *elemIndex == fieldAccess.getFieldIndex())
339 accesses.push_back(fieldAccess);
340 }
341 return accesses;
342}
343
344InstanceOp LowerMemoryPass::emitMemoryInstance(MemOp op, FModuleOp module,
345 const FirMemory &summary) {
346 OpBuilder builder(op);
347 auto *context = &getContext();
348 auto memName = op.getName();
349 if (memName.empty())
350 memName = "mem";
351
352 // Process each port in turn.
353 SmallVector<Type, 8> portTypes;
354 SmallVector<Direction> portDirections;
355 SmallVector<Attribute> portNames;
356 SmallVector<Attribute> domainInfo;
357 DenseMap<Operation *, size_t> returnHolder;
358 mlir::DominanceInfo domInfo(op->getParentOfType<FModuleOp>());
359
360 // The result values of the memory are not necessarily in the same order as
361 // the memory module that we're lowering to. We need to lower the read
362 // ports before the read/write ports, before the write ports.
363 for (unsigned memportKindIdx = 0; memportKindIdx != 3; ++memportKindIdx) {
364 MemOp::PortKind memportKind = MemOp::PortKind::Read;
365 auto *portLabel = "R";
366 switch (memportKindIdx) {
367 default:
368 break;
369 case 1:
370 memportKind = MemOp::PortKind::ReadWrite;
371 portLabel = "RW";
372 break;
373 case 2:
374 memportKind = MemOp::PortKind::Write;
375 portLabel = "W";
376 break;
377 }
378
379 // This is set to the count of the kind of memport we're emitting, for
380 // label names.
381 unsigned portNumber = 0;
382
383 // Get an unsigned type with the specified width.
384 auto getType = [&](size_t width) { return UIntType::get(context, width); };
385 auto ui1Type = getType(1);
386 auto addressType = getType(std::max(1U, llvm::Log2_64_Ceil(summary.depth)));
387 auto dataType = UIntType::get(context, summary.dataWidth);
388 auto clockType = ClockType::get(context);
389
390 // Memories return multiple structs, one for each port, which means we
391 // have two layers of type to split apart.
392 for (size_t i = 0, e = op.getNumResults(); i != e; ++i) {
393 // Process all of one kind before the next.
394 if (memportKind != op.getPortKind(i))
395 continue;
396
397 auto addPort = [&](Direction direction, StringRef field, Type portType) {
398 // Map subfields of the memory port to module ports.
399 auto accesses = getAllFieldAccesses(op.getResult(i), field);
400 for (auto a : accesses)
401 returnHolder[a] = portTypes.size();
402 // Record the new port information.
403 portTypes.push_back(portType);
404 portDirections.push_back(direction);
405 portNames.push_back(
406 builder.getStringAttr(portLabel + Twine(portNumber) + "_" + field));
407 domainInfo.push_back(builder.getArrayAttr({}));
408 };
409
410 auto getDriver = [&](StringRef field) -> Operation * {
411 auto accesses = getAllFieldAccesses(op.getResult(i), field);
412 for (auto a : accesses) {
413 for (auto *user : a->getUsers()) {
414 // If this is a connect driving a value to the field, return it.
415 if (auto connect = dyn_cast<FConnectLike>(user);
416 connect && connect.getDest() == a)
417 return connect;
418 }
419 }
420 return nullptr;
421 };
422
423 // Find the value connected to the enable and 'and' it with the mask,
424 // and then remove the mask entirely. This is used to remove the mask when
425 // it is 1 bit.
426 auto removeMask = [&](StringRef enable, StringRef mask) {
427 // Get the connect which drives a value to the mask element.
428 auto *maskConnect = getDriver(mask);
429 if (!maskConnect)
430 return;
431 // Get the connect which drives a value to the en element
432 auto *enConnect = getDriver(enable);
433 if (!enConnect)
434 return;
435 // Find the proper place to create the And operation. The mask and en
436 // signals must both dominate the new operation.
437 OpBuilder b(maskConnect);
438 if (domInfo.dominates(maskConnect, enConnect))
439 b.setInsertionPoint(enConnect);
440 // 'and' the enable and mask signals together and use it as the enable.
441 auto andOp =
442 AndPrimOp::create(b, op->getLoc(), maskConnect->getOperand(1),
443 enConnect->getOperand(1));
444 enConnect->setOperand(1, andOp);
445 enConnect->moveAfter(andOp);
446 // Erase the old mask connect.
447 auto *maskField = maskConnect->getOperand(0).getDefiningOp();
448 operationsToErase.insert(maskConnect);
449 operationsToErase.insert(maskField);
450 };
451
452 if (memportKind == MemOp::PortKind::Read) {
453 addPort(Direction::In, "addr", addressType);
454 addPort(Direction::In, "en", ui1Type);
455 addPort(Direction::In, "clk", clockType);
456 addPort(Direction::Out, "data", dataType);
457 } else if (memportKind == MemOp::PortKind::ReadWrite) {
458 addPort(Direction::In, "addr", addressType);
459 addPort(Direction::In, "en", ui1Type);
460 addPort(Direction::In, "clk", clockType);
461 addPort(Direction::In, "wmode", ui1Type);
462 addPort(Direction::In, "wdata", dataType);
463 addPort(Direction::Out, "rdata", dataType);
464 // Ignore mask port, if maskBits =1
465 if (summary.isMasked)
466 addPort(Direction::In, "wmask", getType(summary.maskBits));
467 else
468 removeMask("wmode", "wmask");
469 } else {
470 addPort(Direction::In, "addr", addressType);
471 addPort(Direction::In, "en", ui1Type);
472 addPort(Direction::In, "clk", clockType);
473 addPort(Direction::In, "data", dataType);
474 // Ignore mask port, if maskBits == 1
475 if (summary.isMasked)
476 addPort(Direction::In, "mask", getType(summary.maskBits));
477 else
478 removeMask("en", "mask");
479 }
480
481 ++portNumber;
482 }
483 }
484
485 // Create the instance to replace the memop. The instance name matches the
486 // name of the original memory module before deduplication.
487 // TODO: how do we lower port annotations?
488 auto inst = InstanceOp::create(
489 builder, op.getLoc(), portTypes, module.getNameAttr(),
490 summary.getFirMemoryName(), op.getNameKind(), portDirections, portNames,
491 domainInfo,
492 /*annotations=*/ArrayRef<Attribute>(),
493 /*portAnnotations=*/ArrayRef<Attribute>(),
494 /*layers=*/ArrayRef<Attribute>(), /*lowerToBind=*/false,
495 /*doNotPrint=*/false, op.getInnerSymAttr());
496
497 // Update all users of the result of read ports
498 for (auto [subfield, result] : returnHolder) {
499 subfield->getResult(0).replaceAllUsesWith(inst.getResult(result));
500 operationsToErase.insert(subfield);
501 }
502
503 return inst;
504}
505
506LogicalResult LowerMemoryPass::runOnModule(FModuleOp moduleOp) {
507 assert(operationsToErase.empty() && "operationsToErase must be empty");
508
509 auto result = moduleOp.walk([&](MemOp op) {
510 // Check that the memory has been properly lowered already.
511 if (!type_isa<UIntType>(op.getDataType())) {
512 op->emitError("memories should be flattened before running LowerMemory");
513 return WalkResult::interrupt();
514 }
515
516 auto summary = getSummary(op);
517 if (summary.isSeqMem())
518 lowerMemory(op, summary);
519
520 return WalkResult::advance();
521 });
522
523 if (result.wasInterrupted())
524 return failure();
525
526 for (Operation *op : operationsToErase)
527 op->erase();
528
529 operationsToErase.clear();
530
531 return success();
532}
533
534void LowerMemoryPass::runOnOperation() {
535 auto circuit = getOperation();
536 symbolTable = &getAnalysis<SymbolTable>();
537 circuitNamespace.add(circuit);
538
539 // We iterate the circuit from top-to-bottom. This ensures that we get
540 // consistent memory names. (Memory modules will be inserted before the
541 // module we are processing to prevent these being unnecessarily visited.)
542 // All memories are eligible for deduplication with equivalent memories,
543 // regardless of whether they are in the design.
544 for (auto moduleOp : circuit.getBodyBlock()->getOps<FModuleOp>()) {
545 if (failed(runOnModule(moduleOp)))
546 return signalPassFailure();
547 }
548
549 circuitNamespace.clear();
550 symbolTable = nullptr;
551 memories.clear();
552}
assert(baseType &&"element must be base type")
static std::unique_ptr< Context > context
FirMemory getSummary(MemOp op)
static SmallVector< SubfieldOp > getAllFieldAccesses(Value structValue, StringRef field)
static Block * getBodyBlock(FModuleLike mod)
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.
This class provides a read-only projection of an annotation.
AttrClass getMember(StringAttr name) const
Return a member of the annotation.
void setMember(StringAttr name, Attribute value)
Add or set a member of the annotation to a value.
connect(destination, source)
Definition support.py:39
Direction
This represents the direction of a single port.
Definition FIRRTLEnums.h:27
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.
The InstanceGraph op interface, see InstanceGraphInterface.td for more details.
The namespace of a CircuitOp, generally inhabited by modules.
Definition Namespace.h:24
bool isSeqMem() const
Check whether the memory is a seq mem.
Definition FIRRTLOps.h:214
StringAttr getFirMemoryName() const