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