41#include "mlir/IR/Threading.h"
42#include "mlir/Transforms/DialectConversion.h"
43#include "llvm/ADT/STLExtras.h"
45#define DEBUG_TYPE "firrtl-probes-to-signals"
49#define GEN_PASS_DEF_PROBESTOSIGNALS
50#include "circt/Dialect/FIRRTL/Passes.h.inc"
55using namespace firrtl;
63class ProbeVisitor :
public FIRRTLVisitor<ProbeVisitor, LogicalResult> {
68 LogicalResult visit(FModuleLike mod);
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;
83 if (isa<OpenBundleType, OpenVectorType>(type))
84 return err(
"open aggregates not supported");
86 auto refType = dyn_cast<RefType>(type);
90 if (refType.getLayer())
91 return err(
"layer-colored probes not supported");
94 return refType.getType();
98 static FailureOr<Type> mapType(Type type, Location loc) {
102 return *newType ? *newType : type;
106 template <
typename R>
107 static FailureOr<bool> mapRange(R &&range, Location loc,
108 SmallVectorImpl<Type> &newTypes) {
109 newTypes.reserve(llvm::size(range));
111 bool anyConverted =
false;
112 for (
auto type : range) {
113 auto conv = mapType(type, loc);
116 newTypes.emplace_back(*conv);
117 anyConverted |= *conv != type;
123 LogicalResult visitMemoryDebugPortOp(chirrtl::MemoryDebugPortOp op);
128 if (
auto dbgPortOp = dyn_cast<chirrtl::MemoryDebugPortOp>(op))
129 return visitMemoryDebugPortOp(dbgPortOp);
138 if (
auto fop = dyn_cast<Forceable>(op); fop && fop.isForceable())
139 return visitActiveForceableDecl(fop);
145 LogicalResult visitDecl(MemOp op);
146 LogicalResult visitDecl(WireOp op);
147 LogicalResult visitActiveForceableDecl(Forceable fop);
149 LogicalResult visitInstanceLike(Operation *op);
150 LogicalResult visitDecl(InstanceOp op) {
return visitInstanceLike(op); }
151 LogicalResult visitDecl(InstanceChoiceOp op) {
return visitInstanceLike(op); }
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);
161 LogicalResult visitStmt(RefDefineOp op);
164 LogicalResult visitStmt(RefForceOp op) {
165 return op.emitError(
"force not supported");
167 LogicalResult visitStmt(RefForceInitialOp op) {
168 return op.emitError(
"force_initial not supported");
170 LogicalResult visitStmt(RefReleaseOp op) {
171 return op.emitError(
"release not supported");
173 LogicalResult visitStmt(RefReleaseInitialOp op) {
174 return op.emitError(
"release_initial not supported");
179 DenseMap<Value, Value> probeToHWMap;
182 SmallVector<Forceable> forceables;
185 SmallVector<Operation *> toDelete;
199 assert(mod->getNumRegions() == 1);
200 auto &blocks = mod->getRegion(0).getBlocks();
201 return !blocks.empty() ? &blocks.front() :
nullptr;
206LogicalResult ProbeVisitor::visit(FModuleLike mod) {
210 SmallVector<std::pair<size_t, WireOp>> wires;
212 auto portTypes = mod.getPortTypes();
213 auto portLocs = mod.getPortLocationsAttr().getAsRange<Location>();
214 SmallVector<Attribute> newPortTypes;
216 wires.reserve(portTypes.size());
217 newPortTypes.reserve(portTypes.size());
219 bool portsToChange =
false;
220 for (
auto [idx, typeAttr, loc] :
llvm::enumerate(portTypes, portLocs)) {
221 auto type = cast<TypeAttr>(typeAttr);
225 auto newType = *conv;
228 portsToChange =
true;
229 newPortTypes.push_back(TypeAttr::get(newType));
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();
236 newPortTypes.push_back(type);
242 ->walk<mlir::WalkOrder::PreOrder>(
243 [&](Operation *op) -> WalkResult {
return dispatchVisitor(op); })
249 mod.setPortTypesAttr(ArrayAttr::get(mod->getContext(), newPortTypes));
253 for (
auto [arg, typeAttr] :
254 llvm::zip_equal(block->getArguments(), newPortTypes))
255 arg.setType(cast<TypeAttr>(typeAttr).getValue());
258 for (
auto [idx, wire] : wires) {
259 auto arg = block->getArgument(idx);
260 wire.getData().replaceAllUsesWith(arg);
267 for (
auto *op :
llvm::reverse(toDelete))
271 for (
auto fop : forceables)
281LogicalResult ProbeVisitor::visitUnhandledOp(Operation *op) {
282 auto checkType = [&](
auto type) ->
bool {
291 op->emitError(
"unhandled operation needs conversion of type ")
292 << type <<
" to " << *newType;
296 return success(llvm::none_of(op->getOperandTypes(), checkType) &&
297 llvm::none_of(op->getResultTypes(), checkType));
304ProbeVisitor::visitMemoryDebugPortOp(chirrtl::MemoryDebugPortOp op) {
305 auto conv =
convertType(op.getResult().getType(), op.getLoc());
311 auto vectype = type_cast<FVectorType>(type);
316 auto mem = op.getMemory().getDefiningOp<chirrtl::CombMemOp>();
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);
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)");
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)");
342 SmallVector<Value>
data;
343 ImplicitLocOpBuilder builder(op.getLoc(), op);
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());
362 assert(vectype == FVectorType::get(mem.getType().getElementType(),
363 mem.getType().getNumElements()));
364 auto vecData = VectorCreateOp::create(builder, vectype,
data);
371 builder.setInsertionPoint(mem);
372 auto wire = WireOp::create(builder, vectype);
373 builder.setInsertionPointToEnd(mem->getBlock());
375 probeToHWMap[op.getResult()] = wire.getData();
376 toDelete.push_back(op);
384LogicalResult ProbeVisitor::visitDecl(MemOp op) {
386 SmallVector<Type> newTypes;
387 auto needsConv = mapRange(op->getResultTypes(), op->getLoc(), newTypes);
388 if (failed(needsConv))
393 return op.emitError(
"memory has unsupported debug port (memtap)");
396LogicalResult ProbeVisitor::visitDecl(WireOp op) {
397 if (op.isForceable())
398 return visitActiveForceableDecl(op);
400 auto conv =
convertType(op.getDataRaw().getType(), op.getLoc());
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);
416LogicalResult ProbeVisitor::visitActiveForceableDecl(Forceable fop) {
417 assert(fop.isForceable() &&
"must be called on active forceables");
419 auto data = fop.getData();
420 auto conv = mapType(fop.getDataRef().getType(), fop.getLoc());
423 auto newType = *conv;
424 forceables.push_back(fop);
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);
432 data = wire.getData();
434 probeToHWMap[fop.getDataRef()] =
data;
438LogicalResult ProbeVisitor::visitInstanceLike(Operation *op) {
439 SmallVector<Type> newTypes;
440 auto needsConv = mapRange(op->getResultTypes(), op->getLoc(), newTypes);
441 if (failed(needsConv))
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);
457 newResult.setType(newType);
458 probeToHWMap[oldResult] = newResult;
461 toDelete.push_back(op);
469LogicalResult ProbeVisitor::visitStmt(RefDefineOp op) {
474 auto newDest = probeToHWMap.at(op.getDest());
475 auto newSrc = probeToHWMap.at(op.getSrc());
479 assert(!isa<BlockArgument>(newDest));
480 auto *destDefiningOp = newDest.getDefiningOp();
482 if (!newSrc.getParentBlock()->findAncestorOpInBlock(*destDefiningOp)) {
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";
490 auto *destBlock = newDest.getParentBlock();
491 auto builder = ImplicitLocOpBuilder::atBlockEnd(op.getLoc(), destBlock);
493 toDelete.push_back(op);
497LogicalResult ProbeVisitor::visitExpr(RWProbeOp op) {
500 auto conv = mapType(op.getType(), op.getLoc());
503 auto newType = *conv;
504 toDelete.push_back(op);
506 auto ist = irn.
lookup(op.getTarget());
510 ImplicitLocOpBuilder builder(op.getLoc(), op);
511 builder.setInsertionPointAfterValue(ref.getValue());
513 assert(cast<FIRRTLBaseType>(
data.getType()).getPassiveType() ==
514 op.getType().getType());
515 if (newType !=
data.getType()) {
516 auto wire = WireOp::create(builder, newType);
518 data = wire.getData();
520 probeToHWMap[op.getResult()] =
data;
524LogicalResult ProbeVisitor::visitExpr(RefCastOp op) {
525 auto input = probeToHWMap.at(op.getInput());
533 auto conv = mapType(op.getResult().getType(), op.getLoc());
536 auto newType = *conv;
538 ImplicitLocOpBuilder builder(op.getLoc(), op);
539 builder.setInsertionPointAfterValue(input);
540 auto wire = WireOp::create(builder, newType);
542 probeToHWMap[op.getResult()] = wire.getData();
543 toDelete.push_back(op);
547LogicalResult ProbeVisitor::visitExpr(RefSendOp op) {
548 auto conv = mapType(op.getResult().getType(), op.getLoc());
551 auto newType = *conv;
552 toDelete.push_back(op);
555 if (newType == op.getBase().getType()) {
556 probeToHWMap[op.getResult()] = op.getBase();
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();
571LogicalResult ProbeVisitor::visitExpr(RefResolveOp op) {
573 auto val = probeToHWMap.at(op.getRef());
574 op.replaceAllUsesWith(val);
575 toDelete.push_back(op);
579LogicalResult ProbeVisitor::visitExpr(RefSubOp op) {
581 auto val = probeToHWMap.at(op.getInput());
583 ImplicitLocOpBuilder builder(op.getLoc(), op);
584 builder.setInsertionPointAfterValue(op.getInput());
587 probeToHWMap[op.getResult()] = newVal;
588 toDelete.push_back(op);
597struct ProbesToSignalsPass
598 :
public circt::firrtl::impl::ProbesToSignalsBase<ProbesToSignalsPass> {
599 ProbesToSignalsPass() =
default;
600 void runOnOperation()
override;
604void ProbesToSignalsPass::runOnOperation() {
607 SmallVector<Operation *, 0> ops(getOperation().getOps<FModuleLike>());
610 getAnalysis<hw::InnerSymbolTableCollection>()};
612 auto result = failableParallelForEach(&getContext(), ops, [&](Operation *op) {
613 ProbeVisitor visitor(irn);
614 return visitor.visit(cast<FModuleLike>(op));
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)
#define CIRCT_DEBUG_SCOPED_PASS_LOGGER(PASS)
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...