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"
32#define GEN_PASS_DEF_LOWERMEMORY
33#include "circt/Dialect/FIRRTL/Passes.h.inc"
38using namespace firrtl;
42 size_t numReadPorts = 0;
43 size_t numWritePorts = 0;
44 size_t numReadWritePorts = 0;
46 SmallVector<int32_t> writeClockIDs;
48 for (
size_t i = 0, e = op.getNumResults(); i != e; ++i) {
49 auto portKind = op.getPortKind(i);
50 if (portKind == MemOp::PortKind::Read)
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)
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) {
62 clockToLeader.insert({connect.getSrc(), numWritePorts});
64 writeClockIDs.push_back(numWritePorts);
66 writeClockIDs.push_back(result.first->second);
78 auto width = op.getDataType().getBitWidthOrSentinel();
80 op.emitError(
"'firrtl.mem' should have simple type and known width");
91 *seq::symbolizeRUW(
unsigned(op.getRuw())),
102struct LowerMemoryPass
103 :
public circt::firrtl::impl::LowerMemoryBase<LowerMemoryPass> {
107 return moduleNamespaces.try_emplace(moduleOp, moduleOp).first->second;
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,
118 void lowerMemory(MemOp mem,
const FirMemory &summary);
119 LogicalResult runOnModule(FModuleOp moduleOp);
120 void runOnOperation()
override;
123 DenseMap<Operation *, hw::InnerSymbolNamespace> moduleNamespaces;
125 SymbolTable *symbolTable;
129 std::map<FirMemory, FMemModuleOp> memories;
132 SetVector<Operation *> operationsToErase;
137LowerMemoryPass::getMemoryModulePorts(
const FirMemory &mem) {
146 UIntType::get(
context, std::max(1U, llvm::Log2_64_Ceil(mem.
depth)));
148 Location loc = UnknownLoc::get(
context);
151 SmallVector<PortInfo> ports;
153 auto nameAttr = StringAttr::get(
context, name);
155 {nameAttr, type, direction, hw::InnerSymAttr{}, loc, annotations, {}});
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);
165 makePortCommon(
"R", i, addrType);
166 addPort(
"R" + Twine(i) +
"_data", dataType, Direction::Out);
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);
175 addPort(
"RW" + Twine(i) +
"_wmask", maskType, Direction::In);
179 makePortCommon(
"W", i, addrType);
180 addPort(
"W" + Twine(i) +
"_data", dataType, Direction::In);
183 addPort(
"W" + Twine(i) +
"_mask", maskType, Direction::In);
190LowerMemoryPass::emitMemoryModule(MemOp op,
const FirMemory &mem,
191 const SmallVectorImpl<PortInfo> &ports) {
193 StringRef prefix =
"";
195 prefix = mem.
prefix.getValue();
197 circuitNamespace.newName(prefix + mem.
modName.getValue(),
"ext");
198 auto moduleName = StringAttr::get(&getContext(), newName);
201 OpBuilder b(op->getParentOfType<FModuleOp>());
202 ++numCreatedMemModules;
203 auto moduleOp = FMemModuleOp::create(
207 *symbolizeRUWBehavior(
static_cast<uint32_t
>(mem.
readUnderWrite)));
208 SymbolTable::setSymbolVisibility(moduleOp, SymbolTable::Visibility::Private);
213LowerMemoryPass::getOrCreateMemModule(MemOp op,
const FirMemory &summary,
214 const SmallVectorImpl<PortInfo> &ports) {
216 auto it = memories.find(summary);
217 if (it != memories.end())
222 auto moduleOp = emitMemoryModule(op, summary, ports);
225 memories[summary] = moduleOp;
230void LowerMemoryPass::lowerMemory(MemOp mem,
const FirMemory &summary) {
232 auto ports = getMemoryModulePorts(summary);
235 StringRef prefix =
"";
237 prefix = summary.
prefix.getValue();
238 auto newName = circuitNamespace.newName(prefix + mem.getName());
240 auto wrapperName = StringAttr::get(&getContext(), newName);
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);
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());
259 memInst.getResults())) {
260 if (wrapper.getPortDirection(dst.getArgNumber()) == Direction::Out)
261 MatchingConnectOp::create(b, mem->getLoc(), dst, src);
263 MatchingConnectOp::create(b, mem->getLoc(), src, dst);
268 auto inst = emitMemoryInstance(mem, wrapper, summary);
274 auto leafSym = memModule.getModuleNameAttr();
275 auto leafAttr = FlatSymbolRefAttr::get(wrapper.getModuleNameAttr());
279 auto nonlocalAttr = StringAttr::get(
context,
"circt.nonlocal");
280 bool nlaUpdated =
false;
281 SmallVector<Annotation> newMemModAnnos;
286 auto nlaSym = anno.
getMember<FlatSymbolRefAttr>(nonlocalAttr);
290 auto newNLAIter = processedNLAs.find(nlaSym.getAttr());
291 StringAttr newNLAName;
292 if (newNLAIter == processedNLAs.end()) {
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())
302 return getModuleNamespace(mod);
304 newNamepath.push_back(leafAttr);
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;
314 newNLAName = newNLAIter->getSecond();
315 anno.
setMember(
"circt.nonlocal", FlatSymbolRefAttr::get(newNLAName));
317 newMemModAnnos.push_back(anno);
321 memInst.setInnerSymAttr(hw::InnerSymAttr::get(leafSym));
323 newAnnos.addAnnotations(newMemModAnnos);
324 newAnnos.applyToOperation(memInst);
326 operationsToErase.insert(mem);
332 SmallVector<SubfieldOp> accesses;
333 for (
auto *op : structValue.getUsers()) {
334 assert(isa<SubfieldOp>(op));
335 auto fieldAccess = cast<SubfieldOp>(op);
337 fieldAccess.getInput().getType().base().getElementIndex(field);
338 if (elemIndex && *elemIndex == fieldAccess.getFieldIndex())
339 accesses.push_back(fieldAccess);
344InstanceOp LowerMemoryPass::emitMemoryInstance(MemOp op, FModuleOp module,
346 OpBuilder builder(op);
348 auto memName = op.getName();
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>());
363 for (
unsigned memportKindIdx = 0; memportKindIdx != 3; ++memportKindIdx) {
364 MemOp::PortKind memportKind = MemOp::PortKind::Read;
365 auto *portLabel =
"R";
366 switch (memportKindIdx) {
370 memportKind = MemOp::PortKind::ReadWrite;
374 memportKind = MemOp::PortKind::Write;
381 unsigned portNumber = 0;
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)));
388 auto clockType = ClockType::get(
context);
392 for (
size_t i = 0, e = op.getNumResults(); i != e; ++i) {
394 if (memportKind != op.getPortKind(i))
397 auto addPort = [&](
Direction direction, StringRef field, Type portType) {
400 for (
auto a : accesses)
401 returnHolder[a] = portTypes.size();
403 portTypes.push_back(portType);
404 portDirections.push_back(direction);
406 builder.getStringAttr(portLabel + Twine(portNumber) +
"_" + field));
407 domainInfo.push_back(builder.getArrayAttr({}));
410 auto getDriver = [&](StringRef field) -> Operation * {
412 for (
auto a : accesses) {
413 for (
auto *user : a->getUsers()) {
415 if (
auto connect = dyn_cast<FConnectLike>(user);
426 auto removeMask = [&](StringRef enable, StringRef
mask) {
428 auto *maskConnect = getDriver(
mask);
432 auto *enConnect = getDriver(enable);
437 OpBuilder b(maskConnect);
438 if (domInfo.dominates(maskConnect, enConnect))
439 b.setInsertionPoint(enConnect);
442 AndPrimOp::create(b, op->getLoc(), maskConnect->getOperand(1),
443 enConnect->getOperand(1));
444 enConnect->setOperand(1, andOp);
445 enConnect->moveAfter(andOp);
447 auto *maskField = maskConnect->getOperand(0).getDefiningOp();
448 operationsToErase.insert(maskConnect);
449 operationsToErase.insert(maskField);
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);
466 addPort(Direction::In,
"wmask", getType(summary.
maskBits));
468 removeMask(
"wmode",
"wmask");
470 addPort(Direction::In,
"addr", addressType);
471 addPort(Direction::In,
"en", ui1Type);
472 addPort(Direction::In,
"clk", clockType);
473 addPort(Direction::In,
"data", dataType);
476 addPort(Direction::In,
"mask", getType(summary.
maskBits));
478 removeMask(
"en",
"mask");
488 auto inst = InstanceOp::create(
489 builder, op.getLoc(), portTypes, module.getNameAttr(),
492 ArrayRef<Attribute>(),
493 ArrayRef<Attribute>(),
494 ArrayRef<Attribute>(),
false,
495 false, op.getInnerSymAttr());
498 for (
auto [subfield, result] : returnHolder) {
499 subfield->getResult(0).replaceAllUsesWith(inst.getResult(result));
500 operationsToErase.insert(subfield);
506LogicalResult LowerMemoryPass::runOnModule(FModuleOp moduleOp) {
507 assert(operationsToErase.empty() &&
"operationsToErase must be empty");
509 auto result = moduleOp.walk([&](MemOp op) {
511 if (!type_isa<UIntType>(op.getDataType())) {
512 op->emitError(
"memories should be flattened before running LowerMemory");
513 return WalkResult::interrupt();
518 lowerMemory(op, summary);
520 return WalkResult::advance();
523 if (result.wasInterrupted())
526 for (Operation *op : operationsToErase)
529 operationsToErase.clear();
534void LowerMemoryPass::runOnOperation() {
535 auto circuit = getOperation();
536 symbolTable = &getAnalysis<SymbolTable>();
537 circuitNamespace.add(circuit);
544 for (
auto moduleOp : circuit.
getBodyBlock()->getOps<FModuleOp>()) {
545 if (failed(runOnModule(moduleOp)))
546 return signalPassFailure();
549 circuitNamespace.clear();
550 symbolTable =
nullptr;
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)
Direction
This represents the direction of a single port.
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.
bool isSeqMem() const
Check whether the memory is a seq mem.
StringAttr getFirMemoryName() const