CIRCT  20.0.0git
ProbesToSignals.cpp
Go to the documentation of this file.
1 //===- ProbesToSignals.cpp - Probes to Signals ----------------------------===//
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 //
9 // This file defines the ProbesToSignals pass. This pass replaces probes with
10 // signals of the same type. This is not considered a lowering but a
11 // behavior-changing transformation that may break ABI compatibility anywhere
12 // probes are used relevant to ABI.
13 //
14 // Pre-requisites for complete conversion:
15 // * LowerOpenAggs
16 // - Simplifies this pass, Probes are always separate.
17 // * ExpandWhens
18 // - ref.define is "static single connect", and FIRRTL does not have
19 // an equivalent for hardware connections. As a result, probes sent out
20 // from under a "when" cannot be represented currently.
21 //
22 // Suggested:
23 // * Inference passes, especially width inference. Probes infer slightly
24 // differently than non-probes do (must have same width along the chain).
25 //
26 // Forceable and colored probes are not supported.
27 // Specialize layers on or off to remove colored probes first.
28 //
29 // Debug ports on FIRRTL memories are not currently supported,
30 // but CHIRRTL debug ports are handled.
31 //
32 //===----------------------------------------------------------------------===//
33 
34 #define DEBUG_TYPE "firrtl-probes-to-signals"
35 
37 
43 #include "circt/Support/Debug.h"
44 
45 #include "mlir/IR/IRMapping.h"
46 #include "mlir/IR/Threading.h"
47 #include "mlir/Transforms/DialectConversion.h"
48 
49 #include "llvm/ADT/STLExtras.h"
50 
51 namespace circt {
52 namespace firrtl {
53 #define GEN_PASS_DEF_PROBESTOSIGNALS
54 #include "circt/Dialect/FIRRTL/Passes.h.inc"
55 } // namespace firrtl
56 } // namespace circt
57 
58 using namespace circt;
59 using namespace firrtl;
60 
61 //===----------------------------------------------------------------------===//
62 // Probes to Signals
63 //===----------------------------------------------------------------------===//
64 
65 namespace {
66 
67 class ProbeVisitor : public FIRRTLVisitor<ProbeVisitor, LogicalResult> {
68 public:
69  ProbeVisitor() = default;
70 
71  /// Entrypoint.
72  LogicalResult visit(FModuleLike mod);
73 
77 
78  //===--------------------------------------------------------------------===//
79  // Type conversion
80  //===--------------------------------------------------------------------===//
81 
82  /// Return the converted type, null if same, failure on error.
83  static FailureOr<Type> convertType(Type type, Location loc) {
84  auto err = [type, loc](const Twine &message) {
85  return mlir::emitError(loc, message) << ", cannot convert type " << type;
86  };
87  if (isa<OpenBundleType, OpenVectorType>(type))
88  return err("open aggregates not supported");
89 
90  auto refType = dyn_cast<RefType>(type);
91  if (!refType)
92  return Type();
93 
94  if (refType.getForceable())
95  return err("rwprobe not supported");
96 
97  if (refType.getLayer())
98  return err("layer-colored probes not supported");
99 
100  // Otherwise, this maps to the probed type.
101  return refType.getType();
102  }
103 
104  /// Return "target" type, or failure on error.
105  static FailureOr<Type> mapType(Type type, Location loc) {
106  auto newType = convertType(type, loc);
107  if (failed(newType))
108  return failure();
109  return *newType ? *newType : type;
110  }
111 
112  /// Map a range of types, return if changes needed.
113  template <typename R>
114  static FailureOr<bool> mapRange(R &&range, Location loc,
115  SmallVectorImpl<Type> &newTypes) {
116  newTypes.reserve(llvm::size(range));
117 
118  bool anyConverted = false;
119  for (auto type : range) {
120  auto conv = mapType(type, loc);
121  if (failed(conv))
122  return failure();
123  newTypes.emplace_back(*conv);
124  anyConverted |= *conv != type;
125  }
126  return anyConverted;
127  }
128 
129  // CHIRRTL
130  LogicalResult visitMemoryDebugPortOp(chirrtl::MemoryDebugPortOp op);
131 
132  // Visitors
133 
134  LogicalResult visitInvalidOp(Operation *op) {
135  if (auto dbgPortOp = dyn_cast<chirrtl::MemoryDebugPortOp>(op))
136  return visitMemoryDebugPortOp(dbgPortOp);
137 
138  return visitUnhandledOp(op);
139  }
140  LogicalResult visitUnhandledOp(Operation *op);
141 
142  /// Check declarations specifically before forwarding to unhandled.
143  LogicalResult visitUnhandledDecl(Operation *op) {
144  // Check for and reject forceable declarations explicitly.
145  if (auto fop = dyn_cast<Forceable>(op); fop && fop.isForceable())
146  return fop.emitError("forceable declaration not supported");
147  return visitUnhandledOp(op);
148  }
149 
150  // Declarations
151 
152  LogicalResult visitDecl(MemOp op);
153  LogicalResult visitDecl(WireOp op);
154 
155  LogicalResult visitInstanceLike(Operation *op);
156  LogicalResult visitDecl(InstanceOp op) { return visitInstanceLike(op); }
157  LogicalResult visitDecl(InstanceChoiceOp op) { return visitInstanceLike(op); }
158 
159  // Probe operations.
160 
161  LogicalResult visitExpr(RWProbeOp op) {
162  return op.emitError("rwprobe not supported");
163  }
164  LogicalResult visitExpr(RefCastOp op);
165  LogicalResult visitExpr(RefResolveOp op);
166  LogicalResult visitExpr(RefSendOp op);
167  LogicalResult visitExpr(RefSubOp op);
168 
169  LogicalResult visitStmt(RefDefineOp op);
170 
171 private:
172  /// Map from probe-typed Value's to their non-probe equivalent.
173  DenseMap<Value, Value> probeToHWMap;
174 
175  /// Operations to delete.
176  SmallVector<Operation *> toDelete;
177 };
178 
179 } // end namespace
180 
181 //===----------------------------------------------------------------------===//
182 // Visitor: FModuleLike
183 //===----------------------------------------------------------------------===//
184 
185 static Block *getBodyBlock(FModuleLike mod) {
186  // Safety check for below, presently all modules have a region.
187  assert(mod->getNumRegions() == 1);
188  auto &blocks = mod->getRegion(0).getBlocks();
189  return !blocks.empty() ? &blocks.front() : nullptr;
190 }
191 
192 /// Visit a module, converting its ports and internals to use hardware signals
193 /// instead of probes.
194 LogicalResult ProbeVisitor::visit(FModuleLike mod) {
195  // If module has strings describing XMR suffixes for its ports, reject.
196  if (auto internalPaths = mod->getAttrOfType<ArrayAttr>("internalPaths"))
197  return mod.emitError("cannot convert module with internal path");
198 
199  // Ports -> new ports without probe-ness.
200  // For all probe ports, insert non-probe duplex values to use
201  // as their replacement while rewriting. Only if has body.
202  SmallVector<std::pair<size_t, WireOp>> wires;
203 
204  auto portTypes = mod.getPortTypes();
205  auto portLocs = mod.getPortLocationsAttr().getAsRange<Location>();
206  SmallVector<Attribute> newPortTypes;
207 
208  wires.reserve(portTypes.size());
209  newPortTypes.reserve(portTypes.size());
210  auto *block = getBodyBlock(mod);
211  bool portsToChange = false;
212  for (auto [idx, typeAttr, loc] : llvm::enumerate(portTypes, portLocs)) {
213  auto type = cast<TypeAttr>(typeAttr);
214  auto conv = convertType(type.getValue(), loc);
215  if (failed(conv))
216  return failure();
217  auto newType = *conv;
218 
219  if (newType) {
220  portsToChange = true;
221  newPortTypes.push_back(TypeAttr::get(newType));
222  if (block) {
223  auto builder = OpBuilder::atBlockBegin(block);
224  wires.emplace_back(idx, builder.create<WireOp>(loc, newType));
225  probeToHWMap[block->getArgument(idx)] = wires.back().second.getData();
226  }
227  } else
228  newPortTypes.push_back(type);
229  }
230 
231  // Update body, if present.
232  if (block &&
233  block
234  ->walk<mlir::WalkOrder::PreOrder>(
235  [&](Operation *op) -> WalkResult { return dispatchVisitor(op); })
236  .wasInterrupted())
237  return failure();
238 
239  // Update signature and argument types.
240  if (portsToChange) {
241  mod->setAttr(mod.getPortTypesAttrName(),
242  ArrayAttr::get(mod->getContext(), newPortTypes));
243 
244  if (block) {
245  // We may also need to update the types on the block arguments.
246  for (auto [arg, typeAttr] :
247  llvm::zip_equal(block->getArguments(), newPortTypes))
248  arg.setType(cast<TypeAttr>(typeAttr).getValue());
249 
250  // Drop the port stand-ins and RAUW to the block arguments.
251  for (auto [idx, wire] : wires) {
252  auto arg = block->getArgument(idx);
253  wire.getData().replaceAllUsesWith(arg);
254  wire.erase();
255  }
256  }
257  }
258 
259  // Delete operations that were converted.
260  for (auto *op : llvm::reverse(toDelete))
261  op->erase();
262 
263  return success();
264 }
265 
266 //===----------------------------------------------------------------------===//
267 // Visitor: Unhandled
268 //===----------------------------------------------------------------------===//
269 
270 LogicalResult ProbeVisitor::visitUnhandledOp(Operation *op) {
271  auto checkType = [&](auto type) -> bool {
272  // Return if conversion needed (or if error).
273  auto newType = convertType(type, op->getLoc());
274  if (failed(newType))
275  return true;
276  if (!*newType)
277  return false;
278 
279  // Type found that needs to be converted, diagnose.
280  op->emitError("unhandled operation needs conversion of type ")
281  << type << " to " << *newType;
282  return true;
283  };
284 
285  return success(llvm::none_of(op->getOperandTypes(), checkType) &&
286  llvm::none_of(op->getResultTypes(), checkType));
287 }
288 
289 //===----------------------------------------------------------------------===//
290 // Visitor: CHIRRTL
291 //===----------------------------------------------------------------------===//
292 LogicalResult
293 ProbeVisitor::visitMemoryDebugPortOp(chirrtl::MemoryDebugPortOp op) {
294  auto conv = convertType(op.getResult().getType(), op.getLoc());
295  if (failed(conv))
296  return failure();
297  auto type = *conv;
298  assert(type);
299 
300  auto vectype = type_cast<FVectorType>(type);
301 
302  // Just assert the chirrtl memory IR has the expected structure,
303  // if it didn't many things break.
304  // Must be defined in same module, tapped memory must be comb mem.
305  auto mem = op.getMemory().getDefiningOp<chirrtl::CombMemOp>();
306  assert(mem);
307 
308  // The following is adapted from LowerAnnotations.
309  Value clock;
310  for (auto *portOp : mem.getResult().getUsers()) {
311  for (auto result : portOp->getResults()) {
312  for (auto *user : result.getUsers()) {
313  auto accessOp = dyn_cast<chirrtl::MemoryPortAccessOp>(user);
314  if (!accessOp)
315  continue;
316  auto newClock = accessOp.getClock();
317  if (clock && clock != newClock)
318  return mem.emitOpError(
319  "has different clocks on different ports (this is ambiguous "
320  "when compiling without reference types)");
321  clock = newClock;
322  }
323  }
324  }
325  if (!clock)
326  return mem->emitOpError(
327  "does not have an access port to determine a clock connection (this "
328  "is necessary when compiling without reference types)");
329 
330  // Add one port per memory address.
331  SmallVector<Value> data;
332  ImplicitLocOpBuilder builder(op.getLoc(), op);
333 
334  // Insert new ports as late as possible (end of block containing the memory).
335  // This is necessary to preserve ordering of existing ports.
336  builder.setInsertionPointToEnd(mem->getBlock());
337  Type uintType = builder.getType<UIntType>();
338  for (uint64_t i = 0, e = mem.getType().getNumElements(); i != e; ++i) {
339  auto port = builder.create<chirrtl::MemoryPortOp>(
340  mem.getType().getElementType(),
341  chirrtl::CMemoryPortType::get(builder.getContext()), mem.getResult(),
342  MemDirAttr::Read, builder.getStringAttr("memTap_" + Twine(i)),
343  builder.getArrayAttr({}));
344  builder.create<chirrtl::MemoryPortAccessOp>(
345  port.getPort(),
346  builder.create<ConstantOp>(uintType, APSInt::getUnsigned(i)), clock);
347  data.push_back(port.getData());
348  }
349 
350  // Package up all the reads into a vector.
351  assert(vectype == FVectorType::get(mem.getType().getElementType(),
352  mem.getType().getNumElements()));
353  auto vecData = builder.create<VectorCreateOp>(vectype, data);
354 
355  // While the new ports are added as late as possible, the debug port
356  // operation we're replacing likely has users and those are before
357  // the new ports. Add a wire at a point we know dominates this operation
358  // and the new port access operations added above. This will be used for
359  // the existing users of the debug port.
360  builder.setInsertionPoint(mem);
361  auto wire = builder.create<WireOp>(vectype);
362  builder.setInsertionPointToEnd(mem->getBlock());
363  emitConnect(builder, wire.getData(), vecData);
364  probeToHWMap[op.getResult()] = wire.getData();
365  toDelete.push_back(op);
366  return success();
367 }
368 
369 //===----------------------------------------------------------------------===//
370 // Visitor: Declarations
371 //===----------------------------------------------------------------------===//
372 
373 LogicalResult ProbeVisitor::visitDecl(MemOp op) {
374  // Scan for debug ports. These are not supported presently, diagnose.
375  SmallVector<Type> newTypes;
376  auto needsConv = mapRange(op->getResultTypes(), op->getLoc(), newTypes);
377  if (failed(needsConv))
378  return failure();
379  if (!*needsConv)
380  return success();
381 
382  return op.emitError("memory has unsupported debug port (memtap)");
383 }
384 
385 LogicalResult ProbeVisitor::visitDecl(WireOp op) {
386  if (op.isForceable())
387  return op.emitError("forceable declaration not supported");
388 
389  auto conv = convertType(op.getDataRaw().getType(), op.getLoc());
390  if (failed(conv))
391  return failure();
392  auto type = *conv;
393  if (!type) // No conversion needed.
394  return success();
395 
396  // New Wire of converted type.
397  ImplicitLocOpBuilder builder(op.getLoc(), op);
398  auto cloned = cast<WireOp>(builder.clone(*op));
399  cloned->getOpResults().front().setType(type);
400  probeToHWMap[op.getDataRaw()] = cloned.getData();
401  toDelete.push_back(op);
402  return success();
403 }
404 
405 LogicalResult ProbeVisitor::visitInstanceLike(Operation *op) {
406  SmallVector<Type> newTypes;
407  auto needsConv = mapRange(op->getResultTypes(), op->getLoc(), newTypes);
408  if (failed(needsConv))
409  return failure();
410  if (!*needsConv)
411  return success();
412 
413  // New instance with converted types.
414  // Move users of unconverted results to the new operation.
415  ImplicitLocOpBuilder builder(op->getLoc(), op);
416  auto *newInst = builder.clone(*op);
417  for (auto [oldResult, newResult, newType] :
418  llvm::zip_equal(op->getOpResults(), newInst->getOpResults(), newTypes)) {
419  if (newType == oldResult.getType()) {
420  oldResult.replaceAllUsesWith(newResult);
421  continue;
422  }
423 
424  newResult.setType(newType);
425  probeToHWMap[oldResult] = newResult;
426  }
427 
428  toDelete.push_back(op);
429  return success();
430 }
431 
432 //===----------------------------------------------------------------------===//
433 // Visitor: Probe operations
434 //===----------------------------------------------------------------------===//
435 
436 LogicalResult ProbeVisitor::visitStmt(RefDefineOp op) {
437  // ref.define x, y -> connect map(x), map(y)
438  // Be mindful of connect semantics when considering
439  // placement.
440 
441  auto newDest = probeToHWMap.at(op.getDest());
442  auto newSrc = probeToHWMap.at(op.getSrc());
443 
444  // Source must be ancestor of destination block for a connect
445  // to behave the same (generally).
446  assert(!isa<BlockArgument>(newDest));
447  auto *destDefiningOp = newDest.getDefiningOp();
448  assert(destDefiningOp);
449  if (!newSrc.getParentBlock()->findAncestorOpInBlock(*destDefiningOp)) {
450  // Conditional or sending out of a layer...
451  auto diag = op.emitError("unable to convert to equivalent connect");
452  diag.attachNote(op.getDest().getLoc()) << "destination here";
453  diag.attachNote(op.getSrc().getLoc()) << "source here";
454  return diag;
455  }
456 
457  auto *destBlock = newDest.getParentBlock();
458  auto builder = ImplicitLocOpBuilder::atBlockEnd(op.getLoc(), destBlock);
459  emitConnect(builder, newDest, newSrc);
460  toDelete.push_back(op);
461  return success();
462 }
463 
464 LogicalResult ProbeVisitor::visitExpr(RefCastOp op) {
465  auto input = probeToHWMap.at(op.getInput());
466  // Insert wire of the new type, and connect to it.
467 
468  // y = ref.cast x : probe<t1> -> probe<t2>
469  // ->
470  // w = firrtl.wire : t2
471  // emitConnect(w : t2, map(x): t1)
472 
473  auto conv = mapType(op.getResult().getType(), op.getLoc());
474  if (failed(conv))
475  return failure();
476  auto newType = *conv;
477 
478  ImplicitLocOpBuilder builder(op.getLoc(), op);
479  builder.setInsertionPointAfterValue(input);
480  auto wire = builder.create<WireOp>(newType);
481  emitConnect(builder, wire.getData(), input);
482  probeToHWMap[op.getResult()] = wire.getData();
483  toDelete.push_back(op);
484  return success();
485 }
486 
487 LogicalResult ProbeVisitor::visitExpr(RefSendOp op) {
488  auto conv = mapType(op.getResult().getType(), op.getLoc());
489  if (failed(conv))
490  return failure();
491  auto newType = *conv;
492  toDelete.push_back(op);
493 
494  // If the mapped type is same as input, just use that.
495  if (newType == op.getBase().getType()) {
496  probeToHWMap[op.getResult()] = op.getBase();
497  return success();
498  }
499 
500  // Otherwise, need to make this the probed type (passive).
501  // Insert wire of the new type, and connect to it.
502  assert(newType == op.getBase().getType().getPassiveType());
503  ImplicitLocOpBuilder builder(op.getLoc(), op);
504  builder.setInsertionPointAfterValue(op.getBase());
505  auto wire = builder.create<WireOp>(newType);
506  emitConnect(builder, wire.getData(), op.getBase());
507  probeToHWMap[op.getResult()] = wire.getData();
508  return success();
509 }
510 
511 LogicalResult ProbeVisitor::visitExpr(RefResolveOp op) {
512  // ref.resolve x -> map(x)
513  auto val = probeToHWMap.at(op.getRef());
514  op.replaceAllUsesWith(val);
515  toDelete.push_back(op);
516  return success();
517 }
518 
519 LogicalResult ProbeVisitor::visitExpr(RefSubOp op) {
520  // ref.sub x, fieldid -> index(map(x), fieldid)
521  auto val = probeToHWMap.at(op.getInput());
522  assert(val);
523  ImplicitLocOpBuilder builder(op.getLoc(), op);
524  builder.setInsertionPointAfterValue(op.getInput());
525  auto newVal =
526  getValueByFieldID(builder, val, op.getAccessedField().getFieldID());
527  probeToHWMap[op.getResult()] = newVal;
528  toDelete.push_back(op);
529  return success();
530 }
531 
532 //===----------------------------------------------------------------------===//
533 // Pass Infrastructure
534 //===----------------------------------------------------------------------===//
535 
536 namespace {
537 struct ProbesToSignalsPass
538  : public circt::firrtl::impl::ProbesToSignalsBase<ProbesToSignalsPass> {
539  ProbesToSignalsPass() = default;
540  void runOnOperation() override;
541 };
542 } // end anonymous namespace
543 
544 void ProbesToSignalsPass::runOnOperation() {
545  LLVM_DEBUG(debugPassHeader(this) << "\n");
546 
547  SmallVector<Operation *, 0> ops(getOperation().getOps<FModuleLike>());
548 
549  auto result = failableParallelForEach(&getContext(), ops, [&](Operation *op) {
550  ProbeVisitor visitor;
551  return visitor.visit(cast<FModuleLike>(op));
552  });
553 
554  if (result.failed())
555  signalPassFailure();
556 }
557 
558 /// This is the pass constructor.
559 std::unique_ptr<mlir::Pass> circt::firrtl::createProbesToSignalsPass() {
560  return std::make_unique<ProbesToSignalsPass>();
561 }
assert(baseType &&"element must be base type")
static FIRRTLBaseType convertType(FIRRTLBaseType type)
Returns null type if no conversion is needed.
Definition: DropConst.cpp:32
static Block * getBodyBlock(FModuleLike mod)
FIRRTLVisitor allows you to visit all of the expr/stmt/decls with one class declaration.
Direction get(bool isOutput)
Returns an output direction if isOutput is true, otherwise returns an input direction.
Definition: CalyxOps.cpp:55
Value getValueByFieldID(ImplicitLocOpBuilder builder, Value value, unsigned fieldID)
This gets the value targeted by a field id.
void emitConnect(OpBuilder &builder, Location loc, Value lhs, Value rhs)
Emit a connect between two values.
Definition: FIRRTLUtils.cpp:25
std::unique_ptr< mlir::Pass > createProbesToSignalsPass()
This is the pass constructor.
The InstanceGraph op interface, see InstanceGraphInterface.td for more details.
Definition: DebugAnalysis.h:21
llvm::raw_ostream & debugPassHeader(const mlir::Pass *pass, int width=80)
Write a boilerplate header for a pass to the debug stream.
Definition: Debug.cpp:31