CIRCT 20.0.0git
Loading...
Searching...
No Matches
LowerCHIRRTL.cpp
Go to the documentation of this file.
1//===- LowerCHIRRTL.cpp -----------------------------------------*- 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// Transform CHIRRTL memory operations and memory ports into standard FIRRTL
9// memory operations.
10//
11//===----------------------------------------------------------------------===//
12
19#include "circt/Support/LLVM.h"
20#include "mlir/IR/ImplicitLocOpBuilder.h"
21#include "mlir/IR/OperationSupport.h"
22#include "mlir/Pass/Pass.h"
23#include "llvm/ADT/DenseMap.h"
24
25namespace circt {
26namespace firrtl {
27#define GEN_PASS_DEF_LOWERCHIRRTLPASS
28#include "circt/Dialect/FIRRTL/Passes.h.inc"
29} // namespace firrtl
30} // namespace circt
31
32using namespace circt;
33using namespace firrtl;
34using namespace chirrtl;
35
36namespace {
37struct LowerCHIRRTLPass
38 : public circt::firrtl::impl::LowerCHIRRTLPassBase<LowerCHIRRTLPass>,
39 public CHIRRTLVisitor<LowerCHIRRTLPass>,
40 public FIRRTLVisitor<LowerCHIRRTLPass> {
41
42 using FIRRTLVisitor<LowerCHIRRTLPass>::visitDecl;
43 using FIRRTLVisitor<LowerCHIRRTLPass>::visitExpr;
44 using FIRRTLVisitor<LowerCHIRRTLPass>::visitStmt;
45
46 void visitCHIRRTL(CombMemOp op);
47 void visitCHIRRTL(SeqMemOp op);
48 void visitCHIRRTL(MemoryPortOp op);
49 void visitCHIRRTL(MemoryDebugPortOp op);
50 void visitCHIRRTL(MemoryPortAccessOp op);
51 void visitExpr(SubaccessOp op);
52 void visitExpr(SubfieldOp op);
53 void visitExpr(SubindexOp op);
54 void visitStmt(ConnectOp op);
55 void visitStmt(MatchingConnectOp op);
56 void visitUnhandledOp(Operation *op);
57
58 // Chain the CHIRRTL visitor to the FIRRTL visitor.
59 void visitInvalidCHIRRTL(Operation *op) { dispatchVisitor(op); }
60 void visitUnhandledCHIRRTL(Operation *op) { visitUnhandledOp(op); }
61
62 /// Get a the constant 0. This constant is inserted at the beginning of the
63 /// module.
64 Value getConst(unsigned c) {
65 auto &value = constCache[c];
66 if (!value) {
67 auto module = getOperation();
68 auto builder = OpBuilder::atBlockBegin(module.getBodyBlock());
69 auto u1Type = UIntType::get(builder.getContext(), /*width*/ 1);
70 value = builder.create<ConstantOp>(module.getLoc(), u1Type, APInt(1, c));
71 }
72 return value;
73 }
74
75 //// Clear out any stale data.
76 void clear() {
77 constCache.clear();
78 invalidCache.clear();
79 opsToDelete.clear();
80 subfieldDirs.clear();
81 rdataValues.clear();
82 wdataValues.clear();
83 }
84
85 void emitInvalid(ImplicitLocOpBuilder &builder, Value value);
86
87 MemDirAttr inferMemoryPortKind(MemoryPortOp memPort);
88
89 void replaceMem(Operation *op, StringRef name, bool isSequential, RUWAttr ruw,
90 ArrayAttr annotations);
91
92 template <typename OpType, typename... T>
93 void cloneSubindexOpForMemory(OpType op, Value input, T... operands);
94
95 void runOnOperation() override;
96
97 /// Cached constants.
98 DenseMap<unsigned, Value> constCache;
99 DenseMap<Type, Value> invalidCache;
100
101 /// List of operations to delete at the end of the pass.
102 SmallVector<Operation *> opsToDelete;
103
104 /// This tracks how the result of a subfield operation which is indexes a
105 /// MemoryPortOp is used. This is used to track if the subfield operation
106 /// needs to be cloned to access a memories rdata or wdata.
107 DenseMap<Operation *, MemDirAttr> subfieldDirs;
108
109 /// This maps a subfield-like operation from a MemoryPortOp to a new subfield
110 /// operation which can be used to read from the MemoryOp. This is used to
111 /// update any operations to read from the new memory.
112 DenseMap<Value, Value> rdataValues;
113
114 /// This maps a subfield-like operation from a MemoryPortOp to a new subfield
115 /// operation which can be used to write to the memory, the mask value which
116 /// should be set to 1, and the corresponding wmode port of the memory which
117 /// should be set to 1. Not all memories have wmodes, so this field can
118 /// be null. This is used to update operations to write to the new memory.
119 struct WDataInfo {
120 Value data;
121 Value mask;
122 Value mode;
123 };
124 DenseMap<Value, WDataInfo> wdataValues;
125};
126} // end anonymous namespace
127
128/// Performs the callback for each leaf element of a value. This will create
129/// any subindex and subfield operations needed to access the leaf values of the
130/// aggregate value.
131static void forEachLeaf(ImplicitLocOpBuilder &builder, Value value,
132 llvm::function_ref<void(Value)> func) {
133 auto type = value.getType();
134 if (auto bundleType = type_dyn_cast<BundleType>(type)) {
135 for (size_t i = 0, e = bundleType.getNumElements(); i < e; ++i)
136 forEachLeaf(builder, builder.create<SubfieldOp>(value, i), func);
137 } else if (auto vectorType = type_dyn_cast<FVectorType>(type)) {
138 for (size_t i = 0, e = vectorType.getNumElements(); i != e; ++i)
139 forEachLeaf(builder, builder.create<SubindexOp>(value, i), func);
140 } else {
141 func(value);
142 }
143}
144
145/// Drive a value to all leafs of the input aggregate value. This only makes
146/// sense when all leaf values have the same type, since the same value will be
147/// connected to each leaf. This does not work for aggregates with flip types.
148static void connectLeafsTo(ImplicitLocOpBuilder &builder, Value bundle,
149 Value value) {
150 forEachLeaf(builder, bundle,
151 [&](Value leaf) { emitConnect(builder, leaf, value); });
152}
153
154/// Connect each leaf of an aggregate type to invalid. This does not support
155/// aggregates with flip types.
156void LowerCHIRRTLPass::emitInvalid(ImplicitLocOpBuilder &builder, Value value) {
157 auto type = value.getType();
158 auto &invalid = invalidCache[type];
159 if (!invalid) {
160 auto builder = OpBuilder::atBlockBegin(getOperation().getBodyBlock());
161 invalid = builder.create<InvalidValueOp>(getOperation().getLoc(), type);
162 }
163 emitConnect(builder, value, invalid);
164}
165
166/// Converts a CHIRRTL memory port direction to a MemoryOp port type. The
167/// biggest difference is that there is no match for the Infer port type.
168static MemOp::PortKind memDirAttrToPortKind(MemDirAttr direction) {
169 switch (direction) {
170 case MemDirAttr::Read:
171 return MemOp::PortKind::Read;
172 case MemDirAttr::Write:
173 return MemOp::PortKind::Write;
174 case MemDirAttr::ReadWrite:
175 return MemOp::PortKind::ReadWrite;
176 default:
177 llvm_unreachable(
178 "Unhandled MemDirAttr, was the port direction not inferred?");
179 }
180}
181
182/// This function infers the memory direction of each CHIRRTL memory port. Each
183/// memory port has an initial memory direction which is explicitly declared in
184/// the MemoryPortOp, which is used as a starting point. For example, if the
185/// port is declared to be Write, but it is only ever read from, the port will
186/// become a ReadWrite port.
187///
188/// When the memory port is eventually replaced with a memory, we will go from
189/// having a single data value to having separate rdata and wdata values. In
190/// this function we record how the result of each data subfield operation is
191/// used, so that later on we can make sure the SubfieldOp is cloned to index
192/// into the correct rdata and wdata fields of the memory.
193MemDirAttr LowerCHIRRTLPass::inferMemoryPortKind(MemoryPortOp memPort) {
194 // This function does a depth-first walk of the use-lists of the memport
195 // operation to look through subindex operations and find the places where it
196 // is ultimately used. At each node we record how the children ops are using
197 // the result of the current operation. When we are done visiting the current
198 // operation we store how it is used into a global hashtable for later use.
199 // This records how both the MemoryPort and Subfield operations are used.
200 struct StackElement {
201 StackElement(Value value, Value::use_iterator iterator, MemDirAttr mode)
202 : value(value), iterator(iterator), mode(mode) {}
203 Value value;
204 Value::use_iterator iterator;
205 MemDirAttr mode;
206 };
207
208 SmallVector<StackElement> stack;
209 stack.emplace_back(memPort.getData(), memPort.getData().use_begin(),
210 memPort.getDirection());
211 MemDirAttr mode = MemDirAttr::Infer;
212
213 while (!stack.empty()) {
214 auto *iter = &stack.back().iterator;
215 auto end = stack.back().value.use_end();
216 stack.back().mode |= mode;
217
218 while (*iter != end) {
219 auto &element = stack.back();
220 auto &use = *(*iter);
221 auto *user = use.getOwner();
222 ++(*iter);
223 if (isa<SubindexOp, SubfieldOp>(user)) {
224 // We recurse into Subindex ops to find the leaf-uses.
225 auto output = user->getResult(0);
226 stack.emplace_back(output, output.use_begin(), MemDirAttr::Infer);
227 mode = MemDirAttr::Infer;
228 iter = &stack.back().iterator;
229 end = output.use_end();
230 continue;
231 }
232 if (auto subaccessOp = dyn_cast<SubaccessOp>(user)) {
233 // Subaccess has two arguments, the vector and the index. If we are
234 // using the memory port as an index, we can ignore it. If we are using
235 // the memory as the vector, we need to recurse.
236 auto input = subaccessOp.getInput();
237 if (use.get() == input) {
238 auto output = subaccessOp.getResult();
239 stack.emplace_back(output, output.use_begin(), MemDirAttr::Infer);
240 mode = MemDirAttr::Infer;
241 iter = &stack.back().iterator;
242 end = output.use_end();
243 continue;
244 }
245 // Otherwise we are reading from a memory for the index.
246 element.mode |= MemDirAttr::Read;
247 } else if (auto connectOp = dyn_cast<ConnectOp>(user)) {
248 if (use.get() == connectOp.getDest()) {
249 element.mode |= MemDirAttr::Write;
250 } else {
251 element.mode |= MemDirAttr::Read;
252 }
253 } else if (auto connectOp = dyn_cast<MatchingConnectOp>(user)) {
254 if (use.get() == connectOp.getDest()) {
255 element.mode |= MemDirAttr::Write;
256 } else {
257 element.mode |= MemDirAttr::Read;
258 }
259 } else {
260 // Every other use of a memory is a read operation.
261 element.mode |= MemDirAttr::Read;
262 }
263 }
264 mode = stack.back().mode;
265
266 // Store the direction of the current operation in the global map. This will
267 // be used later to determine if this subaccess operation needs to be cloned
268 // into rdata, wdata, and wmask.
269 subfieldDirs[stack.back().value.getDefiningOp()] = mode;
270 stack.pop_back();
271 }
272
273 return mode;
274}
275
276void LowerCHIRRTLPass::replaceMem(Operation *cmem, StringRef name,
277 bool isSequential, RUWAttr ruw,
278 ArrayAttr annotations) {
279 assert(isa<CombMemOp>(cmem) || isa<SeqMemOp>(cmem));
280
281 // We have several early breaks in this function, so we record the CHIRRTL
282 // memory for deletion here.
283 opsToDelete.push_back(cmem);
284 ++numLoweredMems;
285
286 auto cmemType = type_cast<CMemoryType>(cmem->getResult(0).getType());
287 auto depth = cmemType.getNumElements();
288 auto type = cmemType.getElementType();
289
290 // Collect the information from each of the CMemoryPorts.
291 struct PortInfo {
292 StringAttr name;
293 Type type;
294 Attribute annotations;
295 MemOp::PortKind portKind;
296 Operation *cmemPort;
297 };
298 SmallVector<PortInfo, 4> ports;
299 for (auto *user : cmem->getUsers()) {
300 MemOp::PortKind portKind;
301 StringAttr portName;
302 ArrayAttr portAnnos;
303 if (auto cmemoryPort = dyn_cast<MemoryPortOp>(user)) {
304 // Infer the type of memory port we need to create.
305 auto portDirection = inferMemoryPortKind(cmemoryPort);
306
307 // If the memory port is never used, it will have the Infer type and
308 // should just be deleted. TODO: this is mirroring SFC, but should we be
309 // checking for annotations on the memory port before removing it?
310 if (portDirection == MemDirAttr::Infer)
311 continue;
312 portKind = memDirAttrToPortKind(portDirection);
313 portName = cmemoryPort.getNameAttr();
314 portAnnos = cmemoryPort.getAnnotationsAttr();
315 } else if (auto dPort = dyn_cast<MemoryDebugPortOp>(user)) {
316 portKind = MemOp::PortKind::Debug;
317 portName = dPort.getNameAttr();
318 portAnnos = dPort.getAnnotationsAttr();
319 } else {
320 user->emitOpError("unhandled user of chirrtl memory");
321 return;
322 }
323
324 // Add the new port.
325 ports.push_back({portName, MemOp::getTypeForPort(depth, type, portKind),
326 portAnnos, portKind, user});
327 }
328
329 // If there are no valid memory ports, don't create a memory.
330 if (ports.empty()) {
331 ++numPortlessMems;
332 return;
333 }
334
335 // Canonicalize the ports into alphabetical order.
336 llvm::array_pod_sort(ports.begin(), ports.end(),
337 [](const PortInfo *lhs, const PortInfo *rhs) -> int {
338 return lhs->name.getValue().compare(
339 rhs->name.getValue());
340 });
341
342 SmallVector<Attribute, 4> resultNames;
343 SmallVector<Type, 4> resultTypes;
344 SmallVector<Attribute, 4> portAnnotations;
345 for (auto port : ports) {
346 resultNames.push_back(port.name);
347 resultTypes.push_back(port.type);
348 portAnnotations.push_back(port.annotations);
349 }
350
351 // Write latency is always 1, while the read latency depends on the memory
352 // type.
353 auto readLatency = isSequential ? 1 : 0;
354 auto writeLatency = 1;
355
356 // Create the memory.
357 ImplicitLocOpBuilder memBuilder(cmem->getLoc(), cmem);
358 auto symOp = cast<hw::InnerSymbolOpInterface>(cmem);
359 auto memory = memBuilder.create<MemOp>(
360 resultTypes, readLatency, writeLatency, depth, ruw,
361 memBuilder.getArrayAttr(resultNames), name,
362 cmem->getAttrOfType<firrtl::NameKindEnumAttr>("nameKind").getValue(),
363 annotations, memBuilder.getArrayAttr(portAnnotations),
364 symOp.getInnerSymAttr(),
365 cmem->getAttrOfType<firrtl::MemoryInitAttr>("init"),
366 cmem->getAttrOfType<StringAttr>("prefix"));
367 ++numCreatedMems;
368
369 // Process each memory port, initializing the memory port and inferring when
370 // to set the enable signal high.
371 for (unsigned i = 0, e = memory.getNumResults(); i < e; ++i) {
372 auto memoryPort = memory.getResult(i);
373 auto portKind = ports[i].portKind;
374 if (portKind == MemOp::PortKind::Debug) {
375 rdataValues[ports[i].cmemPort->getResult(0)] = memoryPort;
376 continue;
377 }
378 auto cmemoryPort = cast<MemoryPortOp>(ports[i].cmemPort);
379 auto cmemoryPortAccess = cmemoryPort.getAccess();
380
381 // Most fields on the newly created memory will be assigned an initial value
382 // immediately following the memory decl, and then will be assigned a second
383 // value at the location of the CHIRRTL memory port.
384
385 // Initialization at the MemoryOp.
386 ImplicitLocOpBuilder portBuilder(cmemoryPortAccess.getLoc(),
387 cmemoryPortAccess);
388 auto address = memBuilder.create<SubfieldOp>(memoryPort, "addr");
389 emitInvalid(memBuilder, address);
390 auto enable = memBuilder.create<SubfieldOp>(memoryPort, "en");
391 emitConnect(memBuilder, enable, getConst(0));
392 auto clock = memBuilder.create<SubfieldOp>(memoryPort, "clk");
393 emitInvalid(memBuilder, clock);
394
395 // Initialization at the MemoryPortOp.
396 emitConnect(portBuilder, address, cmemoryPortAccess.getIndex());
397 // Sequential+Read ports have a more complicated "enable inference".
398 auto useEnableInference = isSequential && portKind == MemOp::PortKind::Read;
399 auto *addressOp = cmemoryPortAccess.getIndex().getDefiningOp();
400 // If the address value is not something with a "name", then we do not use
401 // enable inference.
402 useEnableInference &=
403 !addressOp || isa<WireOp, NodeOp, RegOp, RegResetOp>(addressOp);
404
405 // Most memory ports just tie their enable line to one.
406 if (!useEnableInference)
407 emitConnect(portBuilder, enable, getConst(1));
408
409 emitConnect(portBuilder, clock, cmemoryPortAccess.getClock());
410
411 if (portKind == MemOp::PortKind::Read) {
412 // Store the read information for updating subfield ops.
413 auto data = memBuilder.create<SubfieldOp>(memoryPort, "data");
414 rdataValues[cmemoryPort.getData()] = data;
415 } else if (portKind == MemOp::PortKind::Write) {
416 // Initialization at the MemoryOp.
417 auto data = memBuilder.create<SubfieldOp>(memoryPort, "data");
418 emitInvalid(memBuilder, data);
419 auto mask = memBuilder.create<SubfieldOp>(memoryPort, "mask");
420 emitInvalid(memBuilder, mask);
421
422 // Initialization at the MemoryPortOp.
423 connectLeafsTo(portBuilder, mask, getConst(0));
424
425 // Store the write information for updating subfield ops.
426 wdataValues[cmemoryPort.getData()] = {data, mask, nullptr};
427 } else if (portKind == MemOp::PortKind::ReadWrite) {
428 // Initialization at the MemoryOp.
429 auto rdata = memBuilder.create<SubfieldOp>(memoryPort, "rdata");
430 auto wmode = memBuilder.create<SubfieldOp>(memoryPort, "wmode");
431 emitConnect(memBuilder, wmode, getConst(0));
432 auto wdata = memBuilder.create<SubfieldOp>(memoryPort, "wdata");
433 emitInvalid(memBuilder, wdata);
434 auto wmask = memBuilder.create<SubfieldOp>(memoryPort, "wmask");
435 emitInvalid(memBuilder, wmask);
436
437 // Initialization at the MemoryPortOp.
438 connectLeafsTo(portBuilder, wmask, getConst(0));
439
440 // Store the read and write information for updating subfield ops.
441 wdataValues[cmemoryPort.getData()] = {wdata, wmask, wmode};
442 rdataValues[cmemoryPort.getData()] = rdata;
443 }
444
445 // Sequential read only memory ports have "enable inference", which
446 // detects when to set the enable high. All other memory ports set the
447 // enable high when the memport is declared. This is higly questionable
448 // logic that is easily defeated. This behaviour depends on the kind of
449 // operation used as the memport index.
450 if (useEnableInference) {
451 auto *indexOp = cmemoryPortAccess.getIndex().getDefiningOp();
452 bool success = false;
453 if (!indexOp) {
454 // TODO: SFC does not infer any enable when using a module port as the
455 // address. This seems like something that should be fixed sooner
456 // rather than later.
457 } else if (isa<WireOp, RegResetOp, RegOp>(indexOp)) {
458 // If the address is a reference, then we set the enable whenever the
459 // address is driven.
460
461 // Find the uses of the address that write a value to it, ignoring the
462 // ones driving an invalid value.
463 auto drivers =
464 make_filter_range(indexOp->getUsers(), [&](Operation *op) {
465 if (auto connectOp = dyn_cast<ConnectOp>(op)) {
466 if (cmemoryPortAccess.getIndex() == connectOp.getDest())
467 return !dyn_cast_or_null<InvalidValueOp>(
468 connectOp.getSrc().getDefiningOp());
469 } else if (auto connectOp = dyn_cast<MatchingConnectOp>(op)) {
470 if (cmemoryPortAccess.getIndex() == connectOp.getDest())
471 return !dyn_cast_or_null<InvalidValueOp>(
472 connectOp.getSrc().getDefiningOp());
473 }
474 return false;
475 });
476
477 // At each location where we drive a value to the index, set the enable.
478 for (auto *driver : drivers) {
479 ImplicitLocOpBuilder builder(driver->getLoc(), driver);
480 emitConnect(builder, enable, getConst(1));
481 success = true;
482 }
483 } else if (isa<NodeOp>(indexOp)) {
484 // If using a Node for the address, then the we place the enable at the
485 // Node op's
486 ImplicitLocOpBuilder builder(indexOp->getLoc(), indexOp);
487 emitConnect(builder, enable, getConst(1));
488 success = true;
489 }
490
491 // If we don't infer any enable points, it is almost always a user error.
492 if (!success)
493 cmemoryPort.emitWarning("memory port is never enabled");
494 }
495 }
496}
497
498void LowerCHIRRTLPass::visitCHIRRTL(CombMemOp combmem) {
499 replaceMem(combmem, combmem.getName(), /*isSequential*/ false,
500 RUWAttr::Undefined, combmem.getAnnotations());
501}
502
503void LowerCHIRRTLPass::visitCHIRRTL(SeqMemOp seqmem) {
504 replaceMem(seqmem, seqmem.getName(), /*isSequential*/ true, seqmem.getRuw(),
505 seqmem.getAnnotations());
506}
507
508void LowerCHIRRTLPass::visitCHIRRTL(MemoryPortOp memPort) {
509 // The memory port is mostly handled while processing the memory.
510 opsToDelete.push_back(memPort);
511}
512
513void LowerCHIRRTLPass::visitCHIRRTL(MemoryDebugPortOp memPort) {
514 // The memory port is mostly handled while processing the memory.
515 opsToDelete.push_back(memPort);
516}
517
518void LowerCHIRRTLPass::visitCHIRRTL(MemoryPortAccessOp memPortAccess) {
519 // The memory port access is mostly handled while processing the memory.
520 opsToDelete.push_back(memPortAccess);
521}
522
523void LowerCHIRRTLPass::visitStmt(ConnectOp connect) {
524 // Check if we are writing to a memory and, if we are, replace the
525 // destination.
526 auto writeIt = wdataValues.find(connect.getDest());
527 if (writeIt != wdataValues.end()) {
528 auto writeData = writeIt->second;
529 connect.getDestMutable().assign(writeData.data);
530 // Assign the write mask.
531 ImplicitLocOpBuilder builder(connect.getLoc(), connect);
532 connectLeafsTo(builder, writeData.mask, getConst(1));
533 // Only ReadWrite memories have a write mode.
534 if (writeData.mode)
535 emitConnect(builder, writeData.mode, getConst(1));
536 }
537 // Check if we are reading from a memory and, if we are, replace the
538 // source.
539 auto readIt = rdataValues.find(connect.getSrc());
540 if (readIt != rdataValues.end()) {
541 auto newSource = readIt->second;
542 connect.getSrcMutable().assign(newSource);
543 }
544}
545
546void LowerCHIRRTLPass::visitStmt(MatchingConnectOp connect) {
547 // Check if we are writing to a memory and, if we are, replace the
548 // destination.
549 auto writeIt = wdataValues.find(connect.getDest());
550 if (writeIt != wdataValues.end()) {
551 auto writeData = writeIt->second;
552 connect.getDestMutable().assign(writeData.data);
553 // Assign the write mask.
554 ImplicitLocOpBuilder builder(connect.getLoc(), connect);
555 connectLeafsTo(builder, writeData.mask, getConst(1));
556 // Only ReadWrite memories have a write mode.
557 if (writeData.mode)
558 emitConnect(builder, writeData.mode, getConst(1));
559 }
560 // Check if we are reading from a memory and, if we are, replace the
561 // source.
562 auto readIt = rdataValues.find(connect.getSrc());
563 if (readIt != rdataValues.end()) {
564 auto newSource = readIt->second;
565 connect.getSrcMutable().assign(newSource);
566 }
567}
568
569/// This function will create clones of subaccess, subindex, and subfield
570/// operations which are indexing a CHIRRTL memory ports that will index into
571/// the new memory's data field. If a subfield result is used to read from a
572/// memory port, it will be cloned to read from the memory's rdata field. If
573/// the subfield is used to write to a memory port, it will be cloned twice to
574/// write to both the wdata and wmask fields. Users of this subfield operation
575/// will be redirected to the appropriate clone when they are visited.
576template <typename OpType, typename... T>
577void LowerCHIRRTLPass::cloneSubindexOpForMemory(OpType op, Value input,
578 T... operands) {
579 // If the subaccess operation has no direction recorded, then it does not
580 // index a CHIRRTL memory and will be left alone.
581 auto it = subfieldDirs.find(op);
582 if (it == subfieldDirs.end()) {
583 // The subaccess operation input could be a debug port of a CHIRRTL memory.
584 // If it exists in the map, create the replacement operation for it.
585 auto iter = rdataValues.find(input);
586 if (iter != rdataValues.end()) {
587 opsToDelete.push_back(op);
588 ImplicitLocOpBuilder builder(op->getLoc(), op);
589 rdataValues[op] = builder.create<OpType>(rdataValues[input], operands...);
590 }
591 return;
592 }
593
594 // All uses of this op will be updated to use the appropriate clone. If the
595 // recorded direction of this subfield is Infer, then the value is not
596 // actually used to read or write from a memory port, and it will be just
597 // removed.
598 opsToDelete.push_back(op);
599
600 auto direction = it->second;
601 ImplicitLocOpBuilder builder(op->getLoc(), op);
602
603 // If the subaccess operation is used to read from a memory port, we need to
604 // clone it to read from the rdata field.
605 if (direction == MemDirAttr::Read || direction == MemDirAttr::ReadWrite) {
606 rdataValues[op] = builder.create<OpType>(rdataValues[input], operands...);
607 }
608
609 // If the subaccess operation is used to write to the memory, we need to clone
610 // it to write to the wdata and the wmask fields.
611 if (direction == MemDirAttr::Write || direction == MemDirAttr::ReadWrite) {
612 auto writeData = wdataValues[input];
613 auto write = builder.create<OpType>(writeData.data, operands...);
614 auto mask = builder.create<OpType>(writeData.mask, operands...);
615 wdataValues[op] = {write, mask, writeData.mode};
616 }
617}
618
619void LowerCHIRRTLPass::visitExpr(SubaccessOp subaccess) {
620 // Check if the subaccess reads from a memory for
621 // the index.
622 auto readIt = rdataValues.find(subaccess.getIndex());
623 if (readIt != rdataValues.end()) {
624 subaccess.getIndexMutable().assign(readIt->second);
625 }
626 // Handle it like normal.
627 cloneSubindexOpForMemory(subaccess, subaccess.getInput(),
628 subaccess.getIndex());
629}
630
631void LowerCHIRRTLPass::visitExpr(SubfieldOp subfield) {
632 cloneSubindexOpForMemory<SubfieldOp>(subfield, subfield.getInput(),
633 subfield.getFieldIndex());
634}
635
636void LowerCHIRRTLPass::visitExpr(SubindexOp subindex) {
637 cloneSubindexOpForMemory<SubindexOp>(subindex, subindex.getInput(),
638 subindex.getIndex());
639}
640
641void LowerCHIRRTLPass::visitUnhandledOp(Operation *op) {
642 // For every operand, check if it is reading from a memory port and
643 // replace it with a read from the new memory.
644 for (auto &operand : op->getOpOperands()) {
645 auto it = rdataValues.find(operand.get());
646 if (it != rdataValues.end()) {
647 operand.set(it->second);
648 }
649 }
650}
651
652void LowerCHIRRTLPass::runOnOperation() {
653 // Walk the entire body of the module and dispatch the visitor on each
654 // function. This will replace all CHIRRTL memories and ports, and update all
655 // uses.
656 getOperation().getBodyBlock()->walk(
657 [&](Operation *op) { dispatchCHIRRTLVisitor(op); });
658
659 // If there are no operations to delete, then we didn't find any CHIRRTL
660 // memories.
661 if (opsToDelete.empty())
662 markAllAnalysesPreserved();
663
664 // Remove the old memories and their ports.
665 while (!opsToDelete.empty())
666 opsToDelete.pop_back_val()->erase();
667
668 // Clear out any cached data.
669 clear();
670}
671
672std::unique_ptr<mlir::Pass> circt::firrtl::createLowerCHIRRTLPass() {
673 return std::make_unique<LowerCHIRRTLPass>();
674}
assert(baseType &&"element must be base type")
static MemOp::PortKind memDirAttrToPortKind(MemDirAttr direction)
Converts a CHIRRTL memory port direction to a MemoryOp port type.
static void connectLeafsTo(ImplicitLocOpBuilder &builder, Value bundle, Value value)
Drive a value to all leafs of the input aggregate value.
static void forEachLeaf(ImplicitLocOpBuilder &builder, Value value, llvm::function_ref< void(Value)> func)
Performs the callback for each leaf element of a value.
static Block * getBodyBlock(FModuleLike mod)
CHIRRTLVisitor is a visitor for CHIRRTL operations.
FIRRTLVisitor allows you to visit all of the expr/stmt/decls with one class declaration.
connect(destination, source)
Definition support.py:39
std::unique_ptr< mlir::Pass > createLowerCHIRRTLPass()
void emitConnect(OpBuilder &builder, Location loc, Value lhs, Value rhs)
Emit a connect between two values.
The InstanceGraph op interface, see InstanceGraphInterface.td for more details.
This holds the name and type that describes the module's ports.