CIRCT 22.0.0git
Loading...
Searching...
No Matches
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// 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
40#include "circt/Support/Debug.h"
41#include "mlir/IR/Threading.h"
42#include "mlir/Transforms/DialectConversion.h"
43#include "llvm/ADT/STLExtras.h"
44
45#define DEBUG_TYPE "firrtl-probes-to-signals"
46
47namespace circt {
48namespace firrtl {
49#define GEN_PASS_DEF_PROBESTOSIGNALS
50#include "circt/Dialect/FIRRTL/Passes.h.inc"
51} // namespace firrtl
52} // namespace circt
53
54using namespace circt;
55using namespace firrtl;
56
57//===----------------------------------------------------------------------===//
58// Probes to Signals
59//===----------------------------------------------------------------------===//
60
61namespace {
62
63class ProbeVisitor : public FIRRTLVisitor<ProbeVisitor, LogicalResult> {
64public:
65 ProbeVisitor(hw::InnerRefNamespace &irn) : irn(irn) {}
66
67 /// Entrypoint.
68 LogicalResult visit(FModuleLike mod);
69
70 using FIRRTLVisitor<ProbeVisitor, LogicalResult>::visitDecl;
71 using FIRRTLVisitor<ProbeVisitor, LogicalResult>::visitExpr;
72 using FIRRTLVisitor<ProbeVisitor, LogicalResult>::visitStmt;
73
74 //===--------------------------------------------------------------------===//
75 // Type conversion
76 //===--------------------------------------------------------------------===//
77
78 /// Return the converted type, null if same, failure on error.
79 static FailureOr<Type> convertType(Type type, Location loc) {
80 auto err = [type, loc](const Twine &message) {
81 return mlir::emitError(loc, message) << ", cannot convert type " << type;
82 };
83 if (isa<OpenBundleType, OpenVectorType>(type))
84 return err("open aggregates not supported");
85
86 auto refType = dyn_cast<RefType>(type);
87 if (!refType)
88 return Type();
89
90 if (refType.getLayer())
91 return err("layer-colored probes not supported");
92
93 // Otherwise, this maps to the probed type.
94 return refType.getType();
95 }
96
97 /// Return "target" type, or failure on error.
98 static FailureOr<Type> mapType(Type type, Location loc) {
99 auto newType = convertType(type, loc);
100 if (failed(newType))
101 return failure();
102 return *newType ? *newType : type;
103 }
104
105 /// Map a range of types, return if changes needed.
106 template <typename R>
107 static FailureOr<bool> mapRange(R &&range, Location loc,
108 SmallVectorImpl<Type> &newTypes) {
109 newTypes.reserve(llvm::size(range));
110
111 bool anyConverted = false;
112 for (auto type : range) {
113 auto conv = mapType(type, loc);
114 if (failed(conv))
115 return failure();
116 newTypes.emplace_back(*conv);
117 anyConverted |= *conv != type;
118 }
119 return anyConverted;
120 }
121
122 // CHIRRTL
123 LogicalResult visitMemoryDebugPortOp(chirrtl::MemoryDebugPortOp op);
124
125 // Visitors
126
127 LogicalResult visitInvalidOp(Operation *op) {
128 if (auto dbgPortOp = dyn_cast<chirrtl::MemoryDebugPortOp>(op))
129 return visitMemoryDebugPortOp(dbgPortOp);
130
131 return visitUnhandledOp(op);
132 }
133 LogicalResult visitUnhandledOp(Operation *op);
134
135 /// Check declarations specifically before forwarding to unhandled.
136 LogicalResult visitUnhandledDecl(Operation *op) {
137 // Check for and handle active forceable declarations.
138 if (auto fop = dyn_cast<Forceable>(op); fop && fop.isForceable())
139 return visitActiveForceableDecl(fop);
140 return visitUnhandledOp(op);
141 }
142
143 // Declarations
144
145 LogicalResult visitDecl(MemOp op);
146 LogicalResult visitDecl(WireOp op);
147 LogicalResult visitActiveForceableDecl(Forceable fop);
148
149 LogicalResult visitInstanceLike(Operation *op);
150 LogicalResult visitDecl(InstanceOp op) { return visitInstanceLike(op); }
151 LogicalResult visitDecl(InstanceChoiceOp op) { return visitInstanceLike(op); }
152
153 // Probe operations.
154
155 LogicalResult visitExpr(RWProbeOp op);
156 LogicalResult visitExpr(RefCastOp op);
157 LogicalResult visitExpr(RefResolveOp op);
158 LogicalResult visitExpr(RefSendOp op);
159 LogicalResult visitExpr(RefSubOp op);
160
161 LogicalResult visitStmt(RefDefineOp op);
162
163 // Force and release operations: reject as unsupported.
164 LogicalResult visitStmt(RefForceOp op) {
165 return op.emitError("force not supported");
166 }
167 LogicalResult visitStmt(RefForceInitialOp op) {
168 return op.emitError("force_initial not supported");
169 }
170 LogicalResult visitStmt(RefReleaseOp op) {
171 return op.emitError("release not supported");
172 }
173 LogicalResult visitStmt(RefReleaseInitialOp op) {
174 return op.emitError("release_initial not supported");
175 }
176
177private:
178 /// Map from probe-typed Value's to their non-probe equivalent.
179 DenseMap<Value, Value> probeToHWMap;
180
181 /// Forceable operations to demote.
182 SmallVector<Forceable> forceables;
183
184 /// Operations to delete.
185 SmallVector<Operation *> toDelete;
186
187 /// Read-only copy of inner-ref namespace for resolving inner refs.
189};
190
191} // end namespace
192
193//===----------------------------------------------------------------------===//
194// Visitor: FModuleLike
195//===----------------------------------------------------------------------===//
196
197static Block *getBodyBlock(FModuleLike mod) {
198 // Safety check for below, presently all modules have a region.
199 assert(mod->getNumRegions() == 1);
200 auto &blocks = mod->getRegion(0).getBlocks();
201 return !blocks.empty() ? &blocks.front() : nullptr;
202}
203
204/// Visit a module, converting its ports and internals to use hardware signals
205/// instead of probes.
206LogicalResult ProbeVisitor::visit(FModuleLike mod) {
207 // Ports -> new ports without probe-ness.
208 // For all probe ports, insert non-probe duplex values to use
209 // as their replacement while rewriting. Only if has body.
210 SmallVector<std::pair<size_t, WireOp>> wires;
211
212 auto portTypes = mod.getPortTypes();
213 auto portLocs = mod.getPortLocationsAttr().getAsRange<Location>();
214 SmallVector<Attribute> newPortTypes;
215
216 wires.reserve(portTypes.size());
217 newPortTypes.reserve(portTypes.size());
218 auto *block = getBodyBlock(mod);
219 bool portsToChange = false;
220 for (auto [idx, typeAttr, loc] : llvm::enumerate(portTypes, portLocs)) {
221 auto type = cast<TypeAttr>(typeAttr);
222 auto conv = convertType(type.getValue(), loc);
223 if (failed(conv))
224 return failure();
225 auto newType = *conv;
226
227 if (newType) {
228 portsToChange = true;
229 newPortTypes.push_back(TypeAttr::get(newType));
230 if (block) {
231 auto builder = OpBuilder::atBlockBegin(block);
232 wires.emplace_back(idx, WireOp::create(builder, loc, newType));
233 probeToHWMap[block->getArgument(idx)] = wires.back().second.getData();
234 }
235 } else
236 newPortTypes.push_back(type);
237 }
238
239 // Update body, if present.
240 if (block &&
241 block
242 ->walk<mlir::WalkOrder::PreOrder>(
243 [&](Operation *op) -> WalkResult { return dispatchVisitor(op); })
244 .wasInterrupted())
245 return failure();
246
247 // Update signature and argument types.
248 if (portsToChange) {
249 mod.setPortTypesAttr(ArrayAttr::get(mod->getContext(), newPortTypes));
250
251 if (block) {
252 // We may also need to update the types on the block arguments.
253 for (auto [arg, typeAttr] :
254 llvm::zip_equal(block->getArguments(), newPortTypes))
255 arg.setType(cast<TypeAttr>(typeAttr).getValue());
256
257 // Drop the port stand-ins and RAUW to the block arguments.
258 for (auto [idx, wire] : wires) {
259 auto arg = block->getArgument(idx);
260 wire.getData().replaceAllUsesWith(arg);
261 wire.erase();
262 }
263 }
264 }
265
266 // Delete operations that were converted.
267 for (auto *op : llvm::reverse(toDelete))
268 op->erase();
269
270 // Demote forceable's.
271 for (auto fop : forceables)
272 firrtl::detail::replaceWithNewForceability(fop, false);
273
274 return success();
275}
276
277//===----------------------------------------------------------------------===//
278// Visitor: Unhandled
279//===----------------------------------------------------------------------===//
280
281LogicalResult ProbeVisitor::visitUnhandledOp(Operation *op) {
282 auto checkType = [&](auto type) -> bool {
283 // Return if conversion needed (or if error).
284 auto newType = convertType(type, op->getLoc());
285 if (failed(newType))
286 return true;
287 if (!*newType)
288 return false;
289
290 // Type found that needs to be converted, diagnose.
291 op->emitError("unhandled operation needs conversion of type ")
292 << type << " to " << *newType;
293 return true;
294 };
295
296 return success(llvm::none_of(op->getOperandTypes(), checkType) &&
297 llvm::none_of(op->getResultTypes(), checkType));
298}
299
300//===----------------------------------------------------------------------===//
301// Visitor: CHIRRTL
302//===----------------------------------------------------------------------===//
303LogicalResult
304ProbeVisitor::visitMemoryDebugPortOp(chirrtl::MemoryDebugPortOp op) {
305 auto conv = convertType(op.getResult().getType(), op.getLoc());
306 if (failed(conv))
307 return failure();
308 auto type = *conv;
309 assert(type);
310
311 auto vectype = type_cast<FVectorType>(type);
312
313 // Just assert the chirrtl memory IR has the expected structure,
314 // if it didn't many things break.
315 // Must be defined in same module, tapped memory must be comb mem.
316 auto mem = op.getMemory().getDefiningOp<chirrtl::CombMemOp>();
317 assert(mem);
318
319 // The following is adapted from LowerAnnotations.
320 Value clock;
321 for (auto *portOp : mem.getResult().getUsers()) {
322 for (auto result : portOp->getResults()) {
323 for (auto *user : result.getUsers()) {
324 auto accessOp = dyn_cast<chirrtl::MemoryPortAccessOp>(user);
325 if (!accessOp)
326 continue;
327 auto newClock = accessOp.getClock();
328 if (clock && clock != newClock)
329 return mem.emitOpError(
330 "has different clocks on different ports (this is ambiguous "
331 "when compiling without reference types)");
332 clock = newClock;
333 }
334 }
335 }
336 if (!clock)
337 return mem->emitOpError(
338 "does not have an access port to determine a clock connection (this "
339 "is necessary when compiling without reference types)");
340
341 // Add one port per memory address.
342 SmallVector<Value> data;
343 ImplicitLocOpBuilder builder(op.getLoc(), op);
344
345 // Insert new ports as late as possible (end of block containing the memory).
346 // This is necessary to preserve ordering of existing ports.
347 builder.setInsertionPointToEnd(mem->getBlock());
348 Type uintType = builder.getType<UIntType>();
349 for (uint64_t i = 0, e = mem.getType().getNumElements(); i != e; ++i) {
350 auto port = chirrtl::MemoryPortOp::create(
351 builder, mem.getType().getElementType(),
352 chirrtl::CMemoryPortType::get(builder.getContext()), mem.getResult(),
353 MemDirAttr::Read, builder.getStringAttr("memTap_" + Twine(i)),
354 builder.getArrayAttr({}));
355 chirrtl::MemoryPortAccessOp::create(
356 builder, port.getPort(),
357 ConstantOp::create(builder, uintType, APSInt::getUnsigned(i)), clock);
358 data.push_back(port.getData());
359 }
360
361 // Package up all the reads into a vector.
362 assert(vectype == FVectorType::get(mem.getType().getElementType(),
363 mem.getType().getNumElements()));
364 auto vecData = VectorCreateOp::create(builder, vectype, data);
365
366 // While the new ports are added as late as possible, the debug port
367 // operation we're replacing likely has users and those are before
368 // the new ports. Add a wire at a point we know dominates this operation
369 // and the new port access operations added above. This will be used for
370 // the existing users of the debug port.
371 builder.setInsertionPoint(mem);
372 auto wire = WireOp::create(builder, vectype);
373 builder.setInsertionPointToEnd(mem->getBlock());
374 emitConnect(builder, wire.getData(), vecData);
375 probeToHWMap[op.getResult()] = wire.getData();
376 toDelete.push_back(op);
377 return success();
378}
379
380//===----------------------------------------------------------------------===//
381// Visitor: Declarations
382//===----------------------------------------------------------------------===//
383
384LogicalResult ProbeVisitor::visitDecl(MemOp op) {
385 // Scan for debug ports. These are not supported presently, diagnose.
386 SmallVector<Type> newTypes;
387 auto needsConv = mapRange(op->getResultTypes(), op->getLoc(), newTypes);
388 if (failed(needsConv))
389 return failure();
390 if (!*needsConv)
391 return success();
392
393 return op.emitError("memory has unsupported debug port (memtap)");
394}
395
396LogicalResult ProbeVisitor::visitDecl(WireOp op) {
397 if (op.isForceable())
398 return visitActiveForceableDecl(op);
399
400 auto conv = convertType(op.getDataRaw().getType(), op.getLoc());
401 if (failed(conv))
402 return failure();
403 auto type = *conv;
404 if (!type) // No conversion needed.
405 return success();
406
407 // New Wire of converted type.
408 ImplicitLocOpBuilder builder(op.getLoc(), op);
409 auto cloned = cast<WireOp>(builder.clone(*op));
410 cloned->getOpResults().front().setType(type);
411 probeToHWMap[op.getDataRaw()] = cloned.getData();
412 toDelete.push_back(op);
413 return success();
414}
415
416LogicalResult ProbeVisitor::visitActiveForceableDecl(Forceable fop) {
417 assert(fop.isForceable() && "must be called on active forceables");
418 // Map rw ref result to normal result.
419 auto data = fop.getData();
420 auto conv = mapType(fop.getDataRef().getType(), fop.getLoc());
421 if (failed(conv))
422 return failure();
423 auto newType = *conv;
424 forceables.push_back(fop);
425
426 assert(newType == data.getType().getPassiveType());
427 if (newType != data.getType()) {
428 ImplicitLocOpBuilder builder(fop.getLoc(), fop);
429 builder.setInsertionPointAfterValue(data);
430 auto wire = WireOp::create(builder, newType);
431 emitConnect(builder, wire.getData(), data);
432 data = wire.getData();
433 }
434 probeToHWMap[fop.getDataRef()] = data;
435 return success();
436}
437
438LogicalResult ProbeVisitor::visitInstanceLike(Operation *op) {
439 SmallVector<Type> newTypes;
440 auto needsConv = mapRange(op->getResultTypes(), op->getLoc(), newTypes);
441 if (failed(needsConv))
442 return failure();
443 if (!*needsConv)
444 return success();
445
446 // New instance with converted types.
447 // Move users of unconverted results to the new operation.
448 ImplicitLocOpBuilder builder(op->getLoc(), op);
449 auto *newInst = builder.clone(*op);
450 for (auto [oldResult, newResult, newType] :
451 llvm::zip_equal(op->getOpResults(), newInst->getOpResults(), newTypes)) {
452 if (newType == oldResult.getType()) {
453 oldResult.replaceAllUsesWith(newResult);
454 continue;
455 }
456
457 newResult.setType(newType);
458 probeToHWMap[oldResult] = newResult;
459 }
460
461 toDelete.push_back(op);
462 return success();
463}
464
465//===----------------------------------------------------------------------===//
466// Visitor: Probe operations
467//===----------------------------------------------------------------------===//
468
469LogicalResult ProbeVisitor::visitStmt(RefDefineOp op) {
470 // ref.define x, y -> connect map(x), map(y)
471 // Be mindful of connect semantics when considering
472 // placement.
473
474 auto newDest = probeToHWMap.at(op.getDest());
475 auto newSrc = probeToHWMap.at(op.getSrc());
476
477 // Source must be ancestor of destination block for a connect
478 // to behave the same (generally).
479 assert(!isa<BlockArgument>(newDest));
480 auto *destDefiningOp = newDest.getDefiningOp();
481 assert(destDefiningOp);
482 if (!newSrc.getParentBlock()->findAncestorOpInBlock(*destDefiningOp)) {
483 // Conditional or sending out of a layer...
484 auto diag = op.emitError("unable to convert to equivalent connect");
485 diag.attachNote(op.getDest().getLoc()) << "destination here";
486 diag.attachNote(op.getSrc().getLoc()) << "source here";
487 return diag;
488 }
489
490 auto *destBlock = newDest.getParentBlock();
491 auto builder = ImplicitLocOpBuilder::atBlockEnd(op.getLoc(), destBlock);
492 emitConnect(builder, newDest, newSrc);
493 toDelete.push_back(op);
494 return success();
495}
496
497LogicalResult ProbeVisitor::visitExpr(RWProbeOp op) {
498 // Handle similar to ref.send but lookup the target
499 // and materialize a value for it (indexing).
500 auto conv = mapType(op.getType(), op.getLoc());
501 if (failed(conv))
502 return failure();
503 auto newType = *conv;
504 toDelete.push_back(op);
505
506 auto ist = irn.lookup(op.getTarget());
507 assert(ist);
508 auto ref = getFieldRefForTarget(ist);
509
510 ImplicitLocOpBuilder builder(op.getLoc(), op);
511 builder.setInsertionPointAfterValue(ref.getValue());
512 auto data = getValueByFieldID(builder, ref.getValue(), ref.getFieldID());
513 assert(cast<FIRRTLBaseType>(data.getType()).getPassiveType() ==
514 op.getType().getType());
515 if (newType != data.getType()) {
516 auto wire = WireOp::create(builder, newType);
517 emitConnect(builder, wire.getData(), data);
518 data = wire.getData();
519 }
520 probeToHWMap[op.getResult()] = data;
521 return success();
522}
523
524LogicalResult ProbeVisitor::visitExpr(RefCastOp op) {
525 auto input = probeToHWMap.at(op.getInput());
526 // Insert wire of the new type, and connect to it.
527
528 // y = ref.cast x : probe<t1> -> probe<t2>
529 // ->
530 // w = firrtl.wire : t2
531 // emitConnect(w : t2, map(x): t1)
532
533 auto conv = mapType(op.getResult().getType(), op.getLoc());
534 if (failed(conv))
535 return failure();
536 auto newType = *conv;
537
538 ImplicitLocOpBuilder builder(op.getLoc(), op);
539 builder.setInsertionPointAfterValue(input);
540 auto wire = WireOp::create(builder, newType);
541 emitConnect(builder, wire.getData(), input);
542 probeToHWMap[op.getResult()] = wire.getData();
543 toDelete.push_back(op);
544 return success();
545}
546
547LogicalResult ProbeVisitor::visitExpr(RefSendOp op) {
548 auto conv = mapType(op.getResult().getType(), op.getLoc());
549 if (failed(conv))
550 return failure();
551 auto newType = *conv;
552 toDelete.push_back(op);
553
554 // If the mapped type is same as input, just use that.
555 if (newType == op.getBase().getType()) {
556 probeToHWMap[op.getResult()] = op.getBase();
557 return success();
558 }
559
560 // Otherwise, need to make this the probed type (passive).
561 // Insert wire of the new type, and connect to it.
562 assert(newType == op.getBase().getType().getPassiveType());
563 ImplicitLocOpBuilder builder(op.getLoc(), op);
564 builder.setInsertionPointAfterValue(op.getBase());
565 auto wire = WireOp::create(builder, newType);
566 emitConnect(builder, wire.getData(), op.getBase());
567 probeToHWMap[op.getResult()] = wire.getData();
568 return success();
569}
570
571LogicalResult ProbeVisitor::visitExpr(RefResolveOp op) {
572 // ref.resolve x -> map(x)
573 auto val = probeToHWMap.at(op.getRef());
574 op.replaceAllUsesWith(val);
575 toDelete.push_back(op);
576 return success();
577}
578
579LogicalResult ProbeVisitor::visitExpr(RefSubOp op) {
580 // ref.sub x, fieldid -> index(map(x), fieldid)
581 auto val = probeToHWMap.at(op.getInput());
582 assert(val);
583 ImplicitLocOpBuilder builder(op.getLoc(), op);
584 builder.setInsertionPointAfterValue(op.getInput());
585 auto newVal =
586 getValueByFieldID(builder, val, op.getAccessedField().getFieldID());
587 probeToHWMap[op.getResult()] = newVal;
588 toDelete.push_back(op);
589 return success();
590}
591
592//===----------------------------------------------------------------------===//
593// Pass Infrastructure
594//===----------------------------------------------------------------------===//
595
596namespace {
597struct ProbesToSignalsPass
598 : public circt::firrtl::impl::ProbesToSignalsBase<ProbesToSignalsPass> {
599 ProbesToSignalsPass() = default;
600 void runOnOperation() override;
601};
602} // end anonymous namespace
603
604void ProbesToSignalsPass::runOnOperation() {
606
607 SmallVector<Operation *, 0> ops(getOperation().getOps<FModuleLike>());
608
609 hw::InnerRefNamespace irn{getAnalysis<SymbolTable>(),
610 getAnalysis<hw::InnerSymbolTableCollection>()};
611
612 auto result = failableParallelForEach(&getContext(), ops, [&](Operation *op) {
613 ProbeVisitor visitor(irn);
614 return visitor.visit(cast<FModuleLike>(op));
615 });
616
617 if (result.failed())
618 signalPassFailure();
619}
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)
#define CIRCT_DEBUG_SCOPED_PASS_LOGGER(PASS)
Definition Debug.h:70
FIRRTLVisitor allows you to visit all of the expr/stmt/decls with one class declaration.
ResultType visitInvalidOp(Operation *op, ExtraArgs... args)
visitInvalidOp is an override point for non-FIRRTL dialect operations.
ResultType visitUnhandledOp(Operation *op, ExtraArgs... args)
visitUnhandledOp is an override point for FIRRTL dialect ops that the concrete visitor didn't bother ...
ResultType visitUnhandledDecl(Operation *op, ExtraArgs... args)
Forceable replaceWithNewForceability(Forceable op, bool forceable, ::mlir::PatternRewriter *rewriter=nullptr)
Replace a Forceable op with equivalent, changing whether forceable.
FieldRef getFieldRefForTarget(const hw::InnerSymTarget &ist)
Get FieldRef pointing to the specified inner symbol target, which must be valid.
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.
The InstanceGraph op interface, see InstanceGraphInterface.td for more details.
This class represents the namespace in which InnerRef's can be resolved.
InnerSymTarget lookup(hw::InnerRefAttr inner) const
Resolve the InnerRef to its target within this namespace, returning empty target if no such name exis...