Skip to content

Commit 8c321f9

Browse files
author
Moritz Scherer
committed
[ImportVerilog] Allow functions to capture values from parent scope
This patch teaches the ImportVerilog conversion to detect and plumb captured references from enclosing scopes into lowered MLIR functions. - **FunctionLowering** - Added `captures` and `captureIndex` to record refs captured from outer scopes. - **Context** - Added `finalizeFunctionBodyCaptures` to extend the function signature with captured `moore::RefType` inputs and replace their in-body uses with the new block arguments. - `convertFunction` now installs a temporary `rvalueReadCallback` that collects refs read from outside the function’s body. - **Expressions** - When lowering subroutine calls, the visitor now appends any captured operands to the call argument list. Captures are verified to be `moore::RefType` values originating from the enclosing (module) region. Together these changes allow nested or free functions to reference module-level signals (`logic`, etc.) correctly in the generated MLIR, with explicit function parameters modeling captured variables.
1 parent 52e3074 commit 8c321f9

File tree

4 files changed

+143
-0
lines changed

4 files changed

+143
-0
lines changed

lib/Conversion/ImportVerilog/Expressions.cpp

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -985,6 +985,55 @@ struct RvalueExprVisitor : public ExprVisitor {
985985
arguments.push_back(value);
986986
}
987987

988+
if (!lowering->captures.empty()) {
989+
auto materializeCaptureAtCall = [&](Value cap) -> Value {
990+
// Captures are expected to be moore::RefType.
991+
auto refTy = dyn_cast<moore::RefType>(cap.getType());
992+
if (!refTy) {
993+
lowering->op.emitError(
994+
"expected captured value to be moore::RefType");
995+
return {};
996+
}
997+
998+
// Expected case: the capture stems from a variable of any parent
999+
// scope. We need to walk up, since definition might be a couple regions
1000+
// up.
1001+
Region *capRegion = [&]() -> Region * {
1002+
if (auto ba = dyn_cast<BlockArgument>(cap))
1003+
return ba.getOwner()->getParent();
1004+
if (auto *def = cap.getDefiningOp())
1005+
return def->getParentRegion();
1006+
return nullptr;
1007+
}();
1008+
1009+
Region *callRegion =
1010+
builder.getBlock() ? builder.getBlock()->getParent() : nullptr;
1011+
1012+
for (Region *r = callRegion; r; r = r->getParentRegion()) {
1013+
if (r == capRegion) {
1014+
// Safe to use the SSA value directly here.
1015+
return cap;
1016+
}
1017+
}
1018+
1019+
// Otherwise we can’t legally rematerialize this capture here.
1020+
lowering->op.emitError()
1021+
<< "cannot materialize captured ref at call site; non-symbol "
1022+
<< "source: "
1023+
<< (cap.getDefiningOp()
1024+
? cap.getDefiningOp()->getName().getStringRef()
1025+
: "<block-arg>");
1026+
return {};
1027+
};
1028+
1029+
for (Value cap : lowering->captures) {
1030+
Value mat = materializeCaptureAtCall(cap);
1031+
if (!mat)
1032+
return {};
1033+
arguments.push_back(mat);
1034+
}
1035+
}
1036+
9881037
// Create the call.
9891038
auto callOp =
9901039
mlir::func::CallOp::create(builder, loc, lowering->op, arguments);

lib/Conversion/ImportVerilog/ImportVerilogInternals.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,8 @@ struct ModuleLowering {
4949
/// Function lowering information.
5050
struct FunctionLowering {
5151
mlir::func::FuncOp op;
52+
llvm::SmallVector<Value, 4> captures;
53+
llvm::DenseMap<Value, unsigned> captureIndex;
5254
};
5355

5456
/// Information about a loops continuation and exit blocks relevant while
@@ -110,6 +112,7 @@ struct Context {
110112
FunctionLowering *
111113
declareFunction(const slang::ast::SubroutineSymbol &subroutine);
112114
LogicalResult convertFunction(const slang::ast::SubroutineSymbol &subroutine);
115+
LogicalResult finalizeFunctionBodyCaptures(FunctionLowering &lowering);
113116

114117
// Convert a statement AST node to MLIR ops.
115118
LogicalResult convertStatement(const slang::ast::Statement &stmt);

lib/Conversion/ImportVerilog/Structure.cpp

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1100,9 +1100,33 @@ Context::convertFunction(const slang::ast::SubroutineSymbol &subroutine) {
11001100
valueSymbols.insert(subroutine.returnValVar, returnVar);
11011101
}
11021102

1103+
// Save previous callback
1104+
auto prevCb = rvalueReadCallback;
1105+
auto prevCbGuard =
1106+
llvm::make_scope_exit([&] { rvalueReadCallback = prevCb; });
1107+
1108+
// Capture this function's captured context directly
1109+
rvalueReadCallback = [lowering, prevCb](moore::ReadOp rop) {
1110+
if (prevCb)
1111+
prevCb(rop); // chain previous callback
1112+
1113+
mlir::Value ref = rop.getInput();
1114+
// Only capture refs defined outside this function’s region
1115+
if (!lowering->op.getBody().isAncestor(ref.getParentRegion())) {
1116+
auto [it, inserted] =
1117+
lowering->captureIndex.try_emplace(ref, lowering->captures.size());
1118+
if (inserted)
1119+
lowering->captures.push_back(ref);
1120+
}
1121+
};
1122+
11031123
if (failed(convertStatement(subroutine.getBody())))
11041124
return failure();
11051125

1126+
// Plumb captures into the function as extra block arguments
1127+
if (failed(finalizeFunctionBodyCaptures(*lowering)))
1128+
return failure();
1129+
11061130
// If there was no explicit return statement provided by the user, insert a
11071131
// default one.
11081132
if (builder.getBlock()) {
@@ -1130,3 +1154,49 @@ Context::convertFunction(const slang::ast::SubroutineSymbol &subroutine) {
11301154
}
11311155
return success();
11321156
}
1157+
1158+
LogicalResult
1159+
Context::finalizeFunctionBodyCaptures(FunctionLowering &lowering) {
1160+
if (lowering.captures.empty())
1161+
return success();
1162+
1163+
MLIRContext *ctx = getContext();
1164+
1165+
// Build new input type list: existing inputs + capture ref types.
1166+
SmallVector<Type> newInputs(lowering.op.getFunctionType().getInputs().begin(),
1167+
lowering.op.getFunctionType().getInputs().end());
1168+
for (Value cap : lowering.captures) {
1169+
// Expect captures to be refs.
1170+
Type capTy = cap.getType();
1171+
if (!isa<moore::RefType>(capTy)) {
1172+
return lowering.op.emitError(
1173+
"expected captured value to be a ref-like type");
1174+
}
1175+
newInputs.push_back(capTy);
1176+
}
1177+
1178+
// Results unchanged.
1179+
auto newFuncTy = FunctionType::get(
1180+
ctx, newInputs, lowering.op.getFunctionType().getResults());
1181+
lowering.op.setFunctionType(newFuncTy);
1182+
1183+
// Add the new block arguments to the entry block.
1184+
Block &entry = lowering.op.getBody().front();
1185+
SmallVector<Value> capArgs;
1186+
capArgs.reserve(lowering.captures.size());
1187+
for (Type t :
1188+
llvm::ArrayRef<Type>(newInputs).take_back(lowering.captures.size())) {
1189+
capArgs.push_back(entry.addArgument(t, lowering.op.getLoc()));
1190+
}
1191+
1192+
// Replace uses of each captured Value *inside the function body* with the new
1193+
// arg. Keep uses outside untouched (e.g., in callers).
1194+
for (auto [cap, idx] : lowering.captureIndex) {
1195+
Value arg = capArgs[idx];
1196+
cap.replaceUsesWithIf(arg, [&](OpOperand &use) {
1197+
return lowering.op->isProperAncestor(use.getOwner());
1198+
});
1199+
}
1200+
1201+
return success();
1202+
}

test/Conversion/ImportVerilog/basic.sv

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3360,3 +3360,24 @@ function automatic shortreal testShortrealLiteral();
33603360
// CHECK-NEXT: return [[TMP]] : !moore.f32
33613361
return test;
33623362
endfunction
3363+
3364+
// CHECK: moore.module @testFunctionCapture() {
3365+
module testFunctionCapture();
3366+
3367+
// CHECK: [[A:%.+]] = moore.variable : <l1>
3368+
logic a;
3369+
3370+
function logic testCapture;
3371+
return a;
3372+
endfunction
3373+
3374+
// CHECK: [[RETURNEDA:%.+]] = func.call @testCapture([[A]]) : (!moore.ref<l1>) -> !moore.l1
3375+
// CHECK: [[B:%.+]] = moore.variable [[RETURNEDA]] : <l1>
3376+
logic b = testCapture();
3377+
3378+
// These checks need to be here since testCapture gets moved to after the variable decl,
3379+
// and file check only checks forward.
3380+
// CHECK: func.func private @testCapture(%arg0: !moore.ref<l1>) -> !moore.l1 {
3381+
// CHECK: [[CAPTUREDA:%.+]] = moore.read %arg0 : <l1>
3382+
// CHECK: return [[CAPTUREDA]] : !moore.l1
3383+
endmodule

0 commit comments

Comments
 (0)