Skip to content

Commit 8f20da9

Browse files
fabianschuikiclaude
andcommitted
[Moore] Add coroutine, call_coroutine ops; lower to LLHD
Add `moore.coroutine` and `moore.call_coroutine` ops to represent SystemVerilog tasks. Tasks differ from functions in that they can suspend execution via timing controls. Update `moore.return` to also be valid inside coroutines. Add MooreToCore lowering: `moore.coroutine` maps to `llhd.coroutine`, `moore.call_coroutine` to `llhd.call_coroutine`, and `moore.return` inside a coroutine to `llhd.return`. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 85a7160 commit 8f20da9

File tree

6 files changed

+355
-2
lines changed

6 files changed

+355
-2
lines changed

include/circt/Dialect/Moore/MooreOps.td

Lines changed: 158 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ include "mlir/Interfaces/InferTypeOpInterface.td"
2121
include "mlir/Interfaces/SideEffectInterfaces.td"
2222
include "mlir/Interfaces/MemorySlotInterfaces.td"
2323
include "mlir/Interfaces/CallInterfaces.td"
24+
include "mlir/Interfaces/FunctionInterfaces.td"
2425

2526
// Base class for the operations in this dialect.
2627
class MooreOp<string mnemonic, list<Trait> traits = []> :
@@ -194,9 +195,9 @@ def ProcedureOp : MooreOp<"procedure", [
194195
}
195196

196197
def ReturnOp : MooreOp<"return", [
197-
Pure, Terminator, HasParent<"ProcedureOp">
198+
Pure, Terminator, ParentOneOf<["ProcedureOp", "CoroutineOp"]>
198199
]> {
199-
let summary = "Return from a procedure";
200+
let summary = "Return from a procedure or coroutine";
200201
let assemblyFormat = [{ attr-dict }];
201202
}
202203

@@ -212,6 +213,161 @@ def UnreachableOp : MooreOp<"unreachable", [Terminator]> {
212213
let assemblyFormat = "attr-dict";
213214
}
214215

216+
//===----------------------------------------------------------------------===//
217+
// Coroutines
218+
//===----------------------------------------------------------------------===//
219+
220+
def CoroutineOp : MooreOp<"coroutine", [
221+
IsolatedFromAbove,
222+
FunctionOpInterface,
223+
Symbol,
224+
RegionKindInterface,
225+
RecursiveMemoryEffects,
226+
]> {
227+
let summary = "Define a coroutine";
228+
let description = [{
229+
The `moore.coroutine` op represents a SystemVerilog task. Tasks differ from
230+
functions in that they can suspend execution via timing controls such as
231+
`@(posedge clk)` or `#10`. This makes them coroutine-like: a call to a
232+
coroutine suspends the calling process until the coroutine returns.
233+
234+
Coroutines are `IsolatedFromAbove` and capture any external variables
235+
explicitly as additional arguments, just like `func.func`. Any signals or
236+
variables from the enclosing module are passed as reference arguments.
237+
238+
Example:
239+
```mlir
240+
moore.coroutine private @waitForClk(%clk: !moore.ref<l1>) {
241+
moore.wait_event {
242+
%0 = moore.read %clk : <l1>
243+
moore.detect_event posedge %0 : l1
244+
}
245+
moore.return
246+
}
247+
```
248+
}];
249+
250+
let arguments = (ins
251+
SymbolNameAttr:$sym_name,
252+
TypeAttrOf<FunctionType>:$function_type,
253+
OptionalAttr<StrAttr>:$sym_visibility,
254+
OptionalAttr<DictArrayAttr>:$arg_attrs,
255+
OptionalAttr<DictArrayAttr>:$res_attrs
256+
);
257+
let results = (outs);
258+
let regions = (region MinSizedRegion<1>:$body);
259+
260+
let hasCustomAssemblyFormat = 1;
261+
262+
let builders = [
263+
OpBuilder<(ins "mlir::StringAttr":$sym_name,
264+
"mlir::TypeAttr":$function_type), [{
265+
build($_builder, $_state, sym_name, function_type,
266+
/*sym_visibility=*/mlir::StringAttr(),
267+
/*arg_attrs=*/nullptr, /*res_attrs=*/nullptr);
268+
}]>,
269+
OpBuilder<(ins "mlir::StringRef":$sym_name,
270+
"mlir::FunctionType":$function_type), [{
271+
build($_builder, $_state,
272+
$_builder.getStringAttr(sym_name),
273+
mlir::TypeAttr::get(function_type),
274+
/*sym_visibility=*/mlir::StringAttr(),
275+
/*arg_attrs=*/nullptr, /*res_attrs=*/nullptr);
276+
}]>,
277+
];
278+
279+
let extraClassDeclaration = [{
280+
static mlir::RegionKind getRegionKind(unsigned index) {
281+
return mlir::RegionKind::SSACFG;
282+
}
283+
284+
/// Returns the argument types of this coroutine.
285+
mlir::ArrayRef<mlir::Type> getArgumentTypes() {
286+
return getFunctionType().getInputs();
287+
}
288+
289+
/// Returns the result types of this coroutine.
290+
mlir::ArrayRef<mlir::Type> getResultTypes() {
291+
return getFunctionType().getResults();
292+
}
293+
294+
/// Verify the type attribute of this coroutine.
295+
mlir::LogicalResult verifyType() {
296+
auto type = getFunctionTypeAttr().getValue();
297+
if (!llvm::isa<mlir::FunctionType>(type))
298+
return emitOpError("requires '") << getFunctionTypeAttrName() <<
299+
"' attribute of function type";
300+
return mlir::success();
301+
}
302+
303+
//===------------------------------------------------------------------===//
304+
// CallableOpInterface
305+
//===------------------------------------------------------------------===//
306+
307+
mlir::Region *getCallableRegion() { return &getBody(); }
308+
}];
309+
}
310+
311+
def CallCoroutineOp : MooreOp<"call_coroutine", [
312+
CallOpInterface,
313+
DeclareOpInterfaceMethods<SymbolUserOpInterface>,
314+
]> {
315+
let summary = "Call a coroutine";
316+
let description = [{
317+
The `moore.call_coroutine` op calls a `moore.coroutine`, which represents a
318+
SystemVerilog task call. The calling process suspends until the coroutine
319+
returns. This is only valid inside a procedure or another coroutine.
320+
321+
Example:
322+
```mlir
323+
moore.call_coroutine @waitForClk(%clk) : (!moore.ref<l1>) -> ()
324+
```
325+
}];
326+
327+
let arguments = (ins FlatSymbolRefAttr:$callee, Variadic<AnyType>:$operands);
328+
let results = (outs Variadic<AnyType>);
329+
330+
let assemblyFormat = [{
331+
$callee `(` $operands `)` attr-dict `:` functional-type(operands, results)
332+
}];
333+
334+
let builders = [
335+
OpBuilder<(ins "CoroutineOp":$coroutine,
336+
CArg<"mlir::ValueRange", "{}">:$operands), [{
337+
build($_builder, $_state, coroutine.getFunctionType().getResults(),
338+
mlir::SymbolRefAttr::get(coroutine), operands);
339+
}]>,
340+
];
341+
342+
let extraClassDeclaration = [{
343+
operand_range getArgOperands() {
344+
return getOperands();
345+
}
346+
MutableOperandRange getArgOperandsMutable() {
347+
return getOperandsMutable();
348+
}
349+
350+
mlir::CallInterfaceCallable getCallableForCallee() {
351+
return (*this)->getAttrOfType<mlir::SymbolRefAttr>("callee");
352+
}
353+
354+
void setCalleeFromCallable(mlir::CallInterfaceCallable callee) {
355+
(*this)->setAttr(getCalleeAttrName(),
356+
llvm::cast<mlir::SymbolRefAttr>(callee));
357+
}
358+
359+
/// CallOpInterface requires ArgAndResultAttrsOpInterface, which needs
360+
/// methods to get/set per-argument and per-result attributes. Call sites
361+
/// don't carry these attributes, so we stub them out as no-ops.
362+
mlir::ArrayAttr getArgAttrsAttr() { return nullptr; }
363+
mlir::ArrayAttr getResAttrsAttr() { return nullptr; }
364+
void setArgAttrsAttr(mlir::ArrayAttr args) {}
365+
void setResAttrsAttr(mlir::ArrayAttr args) {}
366+
mlir::Attribute removeArgAttrsAttr() { return nullptr; }
367+
mlir::Attribute removeResAttrsAttr() { return nullptr; }
368+
}];
369+
}
370+
215371
//===----------------------------------------------------------------------===//
216372
// Declarations
217373
//===----------------------------------------------------------------------===//

lib/Conversion/MooreToCore/MooreToCore.cpp

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -516,6 +516,66 @@ struct ProcedureOpConversion : public OpConversionPattern<ProcedureOp> {
516516
}
517517
};
518518

519+
//===----------------------------------------------------------------------===//
520+
// Coroutine Conversion
521+
//===----------------------------------------------------------------------===//
522+
523+
struct CoroutineOpConversion : public OpConversionPattern<CoroutineOp> {
524+
using OpConversionPattern::OpConversionPattern;
525+
526+
LogicalResult
527+
matchAndRewrite(CoroutineOp op, OpAdaptor adaptor,
528+
ConversionPatternRewriter &rewriter) const override {
529+
auto funcType = op.getFunctionType();
530+
TypeConverter::SignatureConversion sigConversion(funcType.getNumInputs());
531+
for (auto [i, type] : llvm::enumerate(funcType.getInputs())) {
532+
auto converted = typeConverter->convertType(type);
533+
if (!converted)
534+
return failure();
535+
sigConversion.addInputs(i, converted);
536+
}
537+
SmallVector<Type> resultTypes;
538+
if (failed(typeConverter->convertTypes(funcType.getResults(), resultTypes)))
539+
return failure();
540+
541+
auto newFuncType = FunctionType::get(
542+
rewriter.getContext(), sigConversion.getConvertedTypes(), resultTypes);
543+
auto newOp = llhd::CoroutineOp::create(rewriter, op.getLoc(),
544+
op.getSymName(), newFuncType);
545+
newOp.setSymVisibilityAttr(op.getSymVisibilityAttr());
546+
rewriter.inlineRegionBefore(op.getBody(), newOp.getBody(),
547+
newOp.getBody().end());
548+
if (failed(rewriter.convertRegionTypes(&newOp.getBody(), *typeConverter,
549+
&sigConversion)))
550+
return failure();
551+
552+
// Replace moore.return with llhd.return inside the coroutine body.
553+
for (auto returnOp :
554+
llvm::make_early_inc_range(newOp.getBody().getOps<ReturnOp>())) {
555+
rewriter.setInsertionPoint(returnOp);
556+
rewriter.replaceOpWithNewOp<llhd::ReturnOp>(returnOp, ValueRange{});
557+
}
558+
559+
rewriter.eraseOp(op);
560+
return success();
561+
}
562+
};
563+
564+
struct CallCoroutineOpConversion : public OpConversionPattern<CallCoroutineOp> {
565+
using OpConversionPattern::OpConversionPattern;
566+
567+
LogicalResult
568+
matchAndRewrite(CallCoroutineOp op, OpAdaptor adaptor,
569+
ConversionPatternRewriter &rewriter) const override {
570+
SmallVector<Type> convResTypes;
571+
if (failed(typeConverter->convertTypes(op.getResultTypes(), convResTypes)))
572+
return failure();
573+
rewriter.replaceOpWithNewOp<llhd::CallCoroutineOp>(
574+
op, convResTypes, adaptor.getCallee(), adaptor.getOperands());
575+
return success();
576+
}
577+
};
578+
519579
struct WaitEventOpConversion : public OpConversionPattern<WaitEventOp> {
520580
using OpConversionPattern::OpConversionPattern;
521581

@@ -3115,6 +3175,8 @@ static void populateOpConversion(ConversionPatternSet &patterns,
31153175
SVModuleOpConversion,
31163176
InstanceOpConversion,
31173177
ProcedureOpConversion,
3178+
CoroutineOpConversion,
3179+
CallCoroutineOpConversion,
31183180
WaitEventOpConversion,
31193181

31203182
// Patterns of shifting operations.

lib/Dialect/Moore/MooreOps.cpp

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
#include "circt/Dialect/Moore/MooreAttributes.h"
1717
#include "circt/Support/CustomDirectiveImpl.h"
1818
#include "mlir/IR/Builders.h"
19+
#include "mlir/Interfaces/FunctionImplementation.h"
1920
#include "llvm/ADT/APSInt.h"
2021
#include "llvm/ADT/SmallString.h"
2122
#include "llvm/ADT/TypeSwitch.h"
@@ -252,6 +253,67 @@ void InstanceOp::getAsmResultNames(OpAsmSetValueNameFn setNameFn) {
252253
}
253254
}
254255

256+
//===----------------------------------------------------------------------===//
257+
// CoroutineOp
258+
//===----------------------------------------------------------------------===//
259+
260+
ParseResult CoroutineOp::parse(OpAsmParser &parser, OperationState &result) {
261+
auto buildFuncType =
262+
[](Builder &builder, ArrayRef<Type> argTypes, ArrayRef<Type> results,
263+
function_interface_impl::VariadicFlag,
264+
std::string &) { return builder.getFunctionType(argTypes, results); };
265+
266+
return function_interface_impl::parseFunctionOp(
267+
parser, result, /*allowVariadic=*/false,
268+
getFunctionTypeAttrName(result.name), buildFuncType,
269+
getArgAttrsAttrName(result.name), getResAttrsAttrName(result.name));
270+
}
271+
272+
void CoroutineOp::print(OpAsmPrinter &p) {
273+
function_interface_impl::printFunctionOp(
274+
p, *this, /*isVariadic=*/false, getFunctionTypeAttrName(),
275+
getArgAttrsAttrName(), getResAttrsAttrName());
276+
}
277+
278+
//===----------------------------------------------------------------------===//
279+
// CallCoroutineOp
280+
//===----------------------------------------------------------------------===//
281+
282+
LogicalResult
283+
CallCoroutineOp::verifySymbolUses(SymbolTableCollection &symbolTable) {
284+
auto calleeName = getCalleeAttr();
285+
auto coroutine =
286+
symbolTable.lookupNearestSymbolFrom<CoroutineOp>(*this, calleeName);
287+
if (!coroutine)
288+
return emitOpError() << "'" << calleeName.getValue()
289+
<< "' does not reference a valid 'moore.coroutine'";
290+
291+
auto type = coroutine.getFunctionType();
292+
if (type.getNumInputs() != getNumOperands())
293+
return emitOpError() << "has " << getNumOperands()
294+
<< " operands, but callee expects "
295+
<< type.getNumInputs();
296+
297+
for (unsigned i = 0, e = type.getNumInputs(); i != e; ++i)
298+
if (getOperand(i).getType() != type.getInput(i))
299+
return emitOpError() << "operand " << i << " type mismatch: expected "
300+
<< type.getInput(i) << ", got "
301+
<< getOperand(i).getType();
302+
303+
if (type.getNumResults() != getNumResults())
304+
return emitOpError() << "has " << getNumResults()
305+
<< " results, but callee returns "
306+
<< type.getNumResults();
307+
308+
for (unsigned i = 0, e = type.getNumResults(); i != e; ++i)
309+
if (getResult(i).getType() != type.getResult(i))
310+
return emitOpError() << "result " << i << " type mismatch: expected "
311+
<< type.getResult(i) << ", got "
312+
<< getResult(i).getType();
313+
314+
return success();
315+
}
316+
255317
//===----------------------------------------------------------------------===//
256318
// VariableOp
257319
//===----------------------------------------------------------------------===//

test/Conversion/MooreToCore/basic.mlir

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1790,3 +1790,21 @@ func.func @QueuePopFront() -> !moore.i16 {
17901790
// CHECK: return [[POPPED]] : i16
17911791
return %v : !moore.i16
17921792
}
1793+
1794+
// CHECK-LABEL: llhd.coroutine private @myTask
1795+
// CHECK-SAME: (%arg0: !llhd.ref<i1>)
1796+
moore.coroutine private @myTask(%arg0: !moore.ref<l1>) {
1797+
// CHECK: llhd.return
1798+
moore.return
1799+
}
1800+
1801+
// CHECK-LABEL: hw.module @CoroutineLowering
1802+
moore.module @CoroutineLowering() {
1803+
%clk = moore.variable : <l1>
1804+
moore.procedure initial {
1805+
// CHECK: llhd.call_coroutine @myTask(%clk) : (!llhd.ref<i1>) -> ()
1806+
moore.call_coroutine @myTask(%clk) : (!moore.ref<l1>) -> ()
1807+
moore.return
1808+
}
1809+
moore.output
1810+
}

test/Dialect/Moore/basic.mlir

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -562,3 +562,29 @@ func.func @StringOperations(%arg0 : !moore.string, %arg1 : !moore.string) {
562562

563563
return
564564
}
565+
566+
// CHECK-LABEL: moore.coroutine @myTask(%arg0: !moore.ref<l1>)
567+
moore.coroutine @myTask(%arg0: !moore.ref<l1>) {
568+
// CHECK: moore.wait_event
569+
moore.wait_event {
570+
%0 = moore.read %arg0 : <l1>
571+
moore.detect_event posedge %0 : l1
572+
}
573+
// CHECK: moore.return
574+
moore.return
575+
}
576+
577+
// CHECK-LABEL: moore.coroutine private @privateTask()
578+
moore.coroutine private @privateTask() {
579+
moore.return
580+
}
581+
582+
moore.module @CoroutineCallTest() {
583+
%clk = moore.variable : <l1>
584+
moore.procedure initial {
585+
// CHECK: moore.call_coroutine @myTask(%clk) : (!moore.ref<l1>) -> ()
586+
moore.call_coroutine @myTask(%clk) : (!moore.ref<l1>) -> ()
587+
moore.return
588+
}
589+
moore.output
590+
}

0 commit comments

Comments
 (0)