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