34 #define DEBUG_TYPE "firrtl-probes-to-signals"
45 #include "mlir/IR/IRMapping.h"
46 #include "mlir/IR/Threading.h"
47 #include "mlir/Transforms/DialectConversion.h"
49 #include "llvm/ADT/STLExtras.h"
53 #define GEN_PASS_DEF_PROBESTOSIGNALS
54 #include "circt/Dialect/FIRRTL/Passes.h.inc"
58 using namespace circt;
59 using namespace firrtl;
67 class ProbeVisitor :
public FIRRTLVisitor<ProbeVisitor, LogicalResult> {
69 ProbeVisitor() =
default;
72 LogicalResult visit(FModuleLike mod);
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;
87 if (isa<OpenBundleType, OpenVectorType>(type))
88 return err(
"open aggregates not supported");
90 auto refType = dyn_cast<RefType>(type);
94 if (refType.getForceable())
95 return err(
"rwprobe not supported");
97 if (refType.getLayer())
98 return err(
"layer-colored probes not supported");
101 return refType.getType();
105 static FailureOr<Type> mapType(Type type, Location loc) {
109 return *newType ? *newType : type;
113 template <
typename R>
114 static FailureOr<bool> mapRange(R &&range, Location loc,
115 SmallVectorImpl<Type> &newTypes) {
116 newTypes.reserve(llvm::size(range));
118 bool anyConverted =
false;
119 for (
auto type : range) {
120 auto conv = mapType(type, loc);
123 newTypes.emplace_back(*conv);
124 anyConverted |= *conv != type;
130 LogicalResult visitMemoryDebugPortOp(chirrtl::MemoryDebugPortOp op);
134 LogicalResult visitInvalidOp(Operation *op) {
135 if (
auto dbgPortOp = dyn_cast<chirrtl::MemoryDebugPortOp>(op))
136 return visitMemoryDebugPortOp(dbgPortOp);
138 return visitUnhandledOp(op);
140 LogicalResult visitUnhandledOp(Operation *op);
143 LogicalResult visitUnhandledDecl(Operation *op) {
145 if (
auto fop = dyn_cast<Forceable>(op); fop && fop.isForceable())
146 return fop.emitError(
"forceable declaration not supported");
147 return visitUnhandledOp(op);
152 LogicalResult visitDecl(MemOp op);
153 LogicalResult visitDecl(WireOp op);
155 LogicalResult visitInstanceLike(Operation *op);
156 LogicalResult visitDecl(InstanceOp op) {
return visitInstanceLike(op); }
157 LogicalResult visitDecl(InstanceChoiceOp op) {
return visitInstanceLike(op); }
161 LogicalResult visitExpr(RWProbeOp op) {
162 return op.emitError(
"rwprobe not supported");
164 LogicalResult visitExpr(RefCastOp op);
165 LogicalResult visitExpr(RefResolveOp op);
166 LogicalResult visitExpr(RefSendOp op);
167 LogicalResult visitExpr(RefSubOp op);
169 LogicalResult visitStmt(RefDefineOp op);
173 DenseMap<Value, Value> probeToHWMap;
176 SmallVector<Operation *> toDelete;
187 assert(mod->getNumRegions() == 1);
188 auto &blocks = mod->getRegion(0).getBlocks();
189 return !blocks.empty() ? &blocks.front() :
nullptr;
194 LogicalResult ProbeVisitor::visit(FModuleLike mod) {
196 if (
auto internalPaths = mod->getAttrOfType<ArrayAttr>(
"internalPaths"))
197 return mod.emitError(
"cannot convert module with internal path");
202 SmallVector<std::pair<size_t, WireOp>> wires;
204 auto portTypes = mod.getPortTypes();
205 auto portLocs = mod.getPortLocationsAttr().getAsRange<Location>();
206 SmallVector<Attribute> newPortTypes;
208 wires.reserve(portTypes.size());
209 newPortTypes.reserve(portTypes.size());
211 bool portsToChange =
false;
212 for (
auto [idx, typeAttr, loc] : llvm::enumerate(portTypes, portLocs)) {
213 auto type = cast<TypeAttr>(typeAttr);
217 auto newType = *conv;
220 portsToChange =
true;
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();
228 newPortTypes.push_back(type);
234 ->walk<mlir::WalkOrder::PreOrder>(
235 [&](Operation *op) -> WalkResult {
return dispatchVisitor(op); })
241 mod->setAttr(mod.getPortTypesAttrName(),
246 for (
auto [arg, typeAttr] :
247 llvm::zip_equal(block->getArguments(), newPortTypes))
248 arg.setType(cast<TypeAttr>(typeAttr).getValue());
251 for (
auto [idx, wire] : wires) {
252 auto arg = block->getArgument(idx);
253 wire.getData().replaceAllUsesWith(arg);
260 for (
auto *op : llvm::reverse(toDelete))
270 LogicalResult ProbeVisitor::visitUnhandledOp(Operation *op) {
271 auto checkType = [&](
auto type) ->
bool {
280 op->emitError(
"unhandled operation needs conversion of type ")
281 << type <<
" to " << *newType;
285 return success(llvm::none_of(op->getOperandTypes(), checkType) &&
286 llvm::none_of(op->getResultTypes(), checkType));
293 ProbeVisitor::visitMemoryDebugPortOp(chirrtl::MemoryDebugPortOp op) {
294 auto conv =
convertType(op.getResult().getType(), op.getLoc());
300 auto vectype = type_cast<FVectorType>(type);
305 auto mem = op.getMemory().getDefiningOp<chirrtl::CombMemOp>();
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);
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)");
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)");
331 SmallVector<Value>
data;
332 ImplicitLocOpBuilder builder(op.getLoc(), op);
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(),
342 MemDirAttr::Read, builder.getStringAttr(
"memTap_" + Twine(i)),
343 builder.getArrayAttr({}));
344 builder.create<chirrtl::MemoryPortAccessOp>(
346 builder.create<ConstantOp>(uintType, APSInt::getUnsigned(i)), clock);
347 data.push_back(port.getData());
352 mem.getType().getNumElements()));
353 auto vecData = builder.create<VectorCreateOp>(vectype,
data);
360 builder.setInsertionPoint(mem);
361 auto wire = builder.create<WireOp>(vectype);
362 builder.setInsertionPointToEnd(mem->getBlock());
364 probeToHWMap[op.getResult()] = wire.getData();
365 toDelete.push_back(op);
373 LogicalResult ProbeVisitor::visitDecl(MemOp op) {
375 SmallVector<Type> newTypes;
376 auto needsConv = mapRange(op->getResultTypes(), op->getLoc(), newTypes);
377 if (failed(needsConv))
382 return op.emitError(
"memory has unsupported debug port (memtap)");
385 LogicalResult ProbeVisitor::visitDecl(WireOp op) {
386 if (op.isForceable())
387 return op.emitError(
"forceable declaration not supported");
389 auto conv =
convertType(op.getDataRaw().getType(), op.getLoc());
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);
405 LogicalResult ProbeVisitor::visitInstanceLike(Operation *op) {
406 SmallVector<Type> newTypes;
407 auto needsConv = mapRange(op->getResultTypes(), op->getLoc(), newTypes);
408 if (failed(needsConv))
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);
424 newResult.setType(newType);
425 probeToHWMap[oldResult] = newResult;
428 toDelete.push_back(op);
436 LogicalResult ProbeVisitor::visitStmt(RefDefineOp op) {
441 auto newDest = probeToHWMap.at(op.getDest());
442 auto newSrc = probeToHWMap.at(op.getSrc());
446 assert(!isa<BlockArgument>(newDest));
447 auto *destDefiningOp = newDest.getDefiningOp();
449 if (!newSrc.getParentBlock()->findAncestorOpInBlock(*destDefiningOp)) {
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";
457 auto *destBlock = newDest.getParentBlock();
458 auto builder = ImplicitLocOpBuilder::atBlockEnd(op.getLoc(), destBlock);
460 toDelete.push_back(op);
464 LogicalResult ProbeVisitor::visitExpr(RefCastOp op) {
465 auto input = probeToHWMap.at(op.getInput());
473 auto conv = mapType(op.getResult().getType(), op.getLoc());
476 auto newType = *conv;
478 ImplicitLocOpBuilder builder(op.getLoc(), op);
479 builder.setInsertionPointAfterValue(input);
480 auto wire = builder.create<WireOp>(newType);
482 probeToHWMap[op.getResult()] = wire.getData();
483 toDelete.push_back(op);
487 LogicalResult ProbeVisitor::visitExpr(RefSendOp op) {
488 auto conv = mapType(op.getResult().getType(), op.getLoc());
491 auto newType = *conv;
492 toDelete.push_back(op);
495 if (newType == op.getBase().getType()) {
496 probeToHWMap[op.getResult()] = op.getBase();
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();
511 LogicalResult ProbeVisitor::visitExpr(RefResolveOp op) {
513 auto val = probeToHWMap.at(op.getRef());
514 op.replaceAllUsesWith(val);
515 toDelete.push_back(op);
519 LogicalResult ProbeVisitor::visitExpr(RefSubOp op) {
521 auto val = probeToHWMap.at(op.getInput());
523 ImplicitLocOpBuilder builder(op.getLoc(), op);
524 builder.setInsertionPointAfterValue(op.getInput());
527 probeToHWMap[op.getResult()] = newVal;
528 toDelete.push_back(op);
537 struct ProbesToSignalsPass
538 :
public circt::firrtl::impl::ProbesToSignalsBase<ProbesToSignalsPass> {
539 ProbesToSignalsPass() =
default;
540 void runOnOperation()
override;
544 void ProbesToSignalsPass::runOnOperation() {
547 SmallVector<Operation *, 0> ops(getOperation().getOps<FModuleLike>());
549 auto result = failableParallelForEach(&getContext(), ops, [&](Operation *op) {
550 ProbeVisitor visitor;
551 return visitor.visit(cast<FModuleLike>(op));
560 return std::make_unique<ProbesToSignalsPass>();
assert(baseType &&"element must be base type")
static FIRRTLBaseType convertType(FIRRTLBaseType type)
Returns null type if no conversion is needed.
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.
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.
std::unique_ptr< mlir::Pass > createProbesToSignalsPass()
This is the pass constructor.
The InstanceGraph op interface, see InstanceGraphInterface.td for more details.
llvm::raw_ostream & debugPassHeader(const mlir::Pass *pass, int width=80)
Write a boilerplate header for a pass to the debug stream.