[SV] Add sv.func, sv.return and sv.func.call operations (#7003)

This PR adds function call constructs (sv.func, sv.return and sv.func.call) to SV dialect. 

* `sv.func` -- this op is between hw.module and func.func. This op implement interfaces `PortList`, `HWModuleLike` and `FunctionOpInterface`. Similar to hw.module, we can preserve argument names and mix the order of input and output arguments. Following func.func empty body indicates that the op is a declaration. Currently sv/func is defined as "automatic" function in SV.
* `sv.return` -- terminator for sv.func. We cannot reuse neither hw.output nor func.func since they have constraints on parent op(and I think it's not reasonable to loose them). 
* `sv.func.call` and `sv.func.call.procedural`. Two operations are added because output arguments cannot be used non-procedural regions.
This commit is contained in:
Hideto Ueno 2024-05-16 16:52:38 +09:00 committed by GitHub
parent b64af242f2
commit bd259e788a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 598 additions and 11 deletions

View File

@ -17,6 +17,9 @@
#include "mlir/Pass/Pass.h"
namespace circt {
namespace hw {
class HWModuleLike;
} // namespace hw
std::unique_ptr<mlir::Pass>
createTestApplyLoweringOptionPass(llvm::StringRef options);

View File

@ -50,7 +50,16 @@ void printModuleSignature(OpAsmPrinter &p, Operation *op,
ParseResult parseModuleSignature(OpAsmParser &parser,
SmallVectorImpl<PortParse> &args,
TypeAttr &modType);
void printModuleSignatureNew(OpAsmPrinter &p, Region &body,
hw::ModuleType modType,
ArrayRef<Attribute> portAttrs,
ArrayRef<Location> locAttrs);
void printModuleSignatureNew(OpAsmPrinter &p, HWModuleLike op);
void getAsmBlockArgumentNamesImpl(mlir::Region &region,
OpAsmSetValueNameFn setNameFn);
SmallVector<Location> getAllPortLocsImpl(hw::ModuleType modType);
} // namespace module_like_impl
} // namespace hw

View File

@ -22,7 +22,7 @@ def SVDialect : Dialect {
This dialect defines the `sv` dialect, which represents various
SystemVerilog-specific constructs in an AST-like representation.
}];
let dependentDialects = ["circt::comb::CombDialect"];
let dependentDialects = ["circt::comb::CombDialect", "circt::hw::HWDialect"];
let useDefaultTypePrinterParser = 1;
let useDefaultAttributePrinterParser = 1;

View File

@ -12,6 +12,9 @@
include "mlir/IR/EnumAttr.td"
include "mlir/IR/OpAsmInterface.td"
include "mlir/Interfaces/FunctionInterfaces.td"
include "mlir/Interfaces/ControlFlowInterfaces.td"
include "circt/Dialect/Emit/EmitOpInterfaces.td"
//===----------------------------------------------------------------------===//
// Control flow like-operations
@ -881,6 +884,169 @@ def MacroDefOp : SVOp<"macro.def",
}
//===----------------------------------------------------------------------===//
// Function/Call
//===----------------------------------------------------------------------===//
def FuncOp : SVOp<"func",
[IsolatedFromAbove, Symbol, OpAsmOpInterface, ProceduralRegion,
DeclareOpInterfaceMethods<HWModuleLike>,
DeclareOpInterfaceMethods<PortList>,
FunctionOpInterface, HasParent<"mlir::ModuleOp">]> {
let summary = "A SystemVerilog function";
let description = [{
`sv.func` represents SystemVerilog function in IEEE 1800-2017 section 13.4
"Functions". Similar to HW module, it's allowed to mix the order of input
and output arguments. `sv.func` can be used for both function
declaration and definition, i.e. a function without a body
region is a declaration.
In SV there are two representations for function results,
"output argument" and "return value". Currently an output argument
is considered as as a return value if it's is the last argument
and has a special attribute `sv.func.explicitly_returned`.
}];
let arguments = (ins
SymbolNameAttr:$sym_name,
TypeAttrOf<ModuleType>:$module_type,
OptionalAttr<DictArrayAttr>:$per_argument_attrs,
OptionalAttr<LocationArrayAttr>:$input_locs, // Null for actual definition.
OptionalAttr<LocationArrayAttr>:$result_locs,
OptionalAttr<StrAttr>:$verilogName
);
let results = (outs);
let regions = (region AnyRegion:$body);
let hasCustomAssemblyFormat = 1;
let extraClassDeclaration = [{
static mlir::StringRef getExplicitlyReturnedAttrName() {
return "sv.func.explicitly_returned";
}
mlir::FunctionType getFunctionType() {
return getModuleType().getFuncType();
}
void setFunctionTypeAttr(mlir::TypeAttr mlirType) {
setModuleType(cast<hw::ModuleType>(mlirType.getValue()));
}
/// Returns the argument types of this function.
ArrayRef<Type> getArgumentTypes() { return getFunctionType().getInputs(); }
/// Returns the result types of this function.
ArrayRef<Type> getResultTypes() { return getFunctionType().getResults(); }
Type getExplicitlyReturnedType();
size_t getNumOutputs() {
return getResultTypes().size();
}
size_t getNumInputs() {
return getArgumentTypes().size();
}
::mlir::Region *getCallableRegion() { return isExternal() ? nullptr : &getBody(); }
bool isDeclaration() { return isExternal(); }
/// OpAsmInterface
void getAsmBlockArgumentNames(mlir::Region &region,
mlir::OpAsmSetValueNameFn setNameFn);
SmallVector<hw::PortInfo> getPortList(bool excludeExplicitReturn);
}];
let extraClassDefinition = [{
hw::ModuleType $cppClass::getHWModuleType() {
return getModuleType();
}
void $cppClass::setHWModuleType(hw::ModuleType type) {
return setModuleType(type);
}
void $cppClass::setAllPortNames(llvm::ArrayRef<mlir::Attribute>) {
}
size_t $cppClass::getNumPorts() {
auto modty = getHWModuleType();
return modty.getNumPorts();
}
size_t $cppClass::getNumInputPorts() {
auto modty = getHWModuleType();
return modty.getNumInputs();
}
size_t $cppClass::getNumOutputPorts() {
auto modty = getHWModuleType();
return modty.getNumOutputs();
}
size_t $cppClass::getPortIdForInputId(size_t idx) {
auto modty = getHWModuleType();
return modty.getPortIdForInputId(idx);
}
size_t $cppClass::getPortIdForOutputId(size_t idx) {
auto modty = getHWModuleType();
return modty.getPortIdForOutputId(idx);
}
}];
}
class SVFuncCallBase<string mnemonic, list<Trait> traits = []>: SVOp<mnemonic,
traits # [CallOpInterface, DeclareOpInterfaceMethods<SymbolUserOpInterface>]> {
let arguments = (ins FlatSymbolRefAttr:$callee, Variadic<AnyType>:$inputs);
let results = (outs Variadic<AnyType>);
let assemblyFormat =
"$callee `(` $inputs `)` attr-dict `:` functional-type($inputs, results)";
let extraClassDeclaration = [{
Value getExplicitlyReturnedValue(sv::FuncOp op);
operand_range getArgOperands() {
return getInputs();
}
MutableOperandRange getArgOperandsMutable() {
return getInputsMutable();
}
mlir::CallInterfaceCallable getCallableForCallee() {
return (*this)->getAttrOfType<mlir::SymbolRefAttr>("callee");
}
/// Set the callee for this operation.
void setCalleeFromCallable(mlir::CallInterfaceCallable callee) {
(*this)->setAttr(getCalleeAttrName(), callee.get<mlir::SymbolRefAttr>());
}
}];
}
def FuncCallProceduralOp : SVFuncCallBase<"func.call.procedural", [ProceduralOp]> {
let summary = "Function call in a procedural region";
}
def FuncCallOp : SVFuncCallBase<"func.call", [NonProceduralOp]> {
let summary = "Function call in a non-procedural region";
let description = [{
This op represents a function call in a non-procedural region.
A function call in a non-procedural region must have a return
value and no output argument.
}];
}
def ReturnOp : SVOp<"return",
[Pure, ReturnLike, Terminator, HasParent<"FuncOp">]> {
let summary = "Function return operation";
let arguments = (ins Variadic<AnyType>:$operands);
let hasCustomAssemblyFormat = 1;
let assemblyFormat = "attr-dict ($operands^ `:` type($operands))?";
let hasVerifier = 1;
}
//===----------------------------------------------------------------------===//
// SV output control.
//===----------------------------------------------------------------------===//

View File

@ -39,11 +39,12 @@ public:
AlwaysCombOp, AlwaysFFOp, InitialOp, CaseOp,
// Other Statements.
AssignOp, BPAssignOp, PAssignOp, ForceOp, ReleaseOp, AliasOp,
FWriteOp, SystemFunctionOp, VerbatimOp,
FWriteOp, SystemFunctionOp, VerbatimOp, FuncCallOp,
FuncCallProceduralOp, ReturnOp,
// Type declarations.
InterfaceOp, InterfaceSignalOp, InterfaceModportOp,
InterfaceInstanceOp, GetModportOp, AssignInterfaceSignalOp,
ReadInterfaceSignalOp, MacroDeclOp, MacroDefOp,
ReadInterfaceSignalOp, MacroDeclOp, MacroDefOp, FuncOp,
// Verification statements.
AssertOp, AssumeOp, CoverOp, AssertConcurrentOp, AssumeConcurrentOp,
CoverConcurrentOp,
@ -127,6 +128,9 @@ public:
HANDLE(AliasOp, Unhandled);
HANDLE(FWriteOp, Unhandled);
HANDLE(SystemFunctionOp, Unhandled);
HANDLE(FuncCallProceduralOp, Unhandled);
HANDLE(FuncCallOp, Unhandled);
HANDLE(ReturnOp, Unhandled);
HANDLE(VerbatimOp, Unhandled);
// Type declarations.
@ -139,6 +143,7 @@ public:
HANDLE(ReadInterfaceSignalOp, Unhandled);
HANDLE(MacroDefOp, Unhandled);
HANDLE(MacroDeclOp, Unhandled);
HANDLE(FuncOp, Unhandled);
// Verification statements.
HANDLE(AssertOp, Unhandled);

View File

@ -391,20 +391,21 @@ static const char *directionAsString(ModulePort::Direction dir) {
abort();
return "unknown";
}
void module_like_impl::printModuleSignatureNew(OpAsmPrinter &p,
HWModuleLike op) {
hw::HWModuleLike op) {
module_like_impl::printModuleSignatureNew(
p, op.getModuleBody(), op.getHWModuleType(), op.getAllPortAttrs(),
op.getAllPortLocs());
}
Region &body = op.getModuleBody();
void module_like_impl::printModuleSignatureNew(OpAsmPrinter &p, Region &body,
hw::ModuleType modType,
ArrayRef<Attribute> portAttrs,
ArrayRef<Location> locAttrs) {
bool isExternal = body.empty();
SmallString<32> resultNameStr;
mlir::OpPrintingFlags flags;
unsigned curArg = 0;
auto modType = op.getHWModuleType();
auto portAttrs = op.getAllPortAttrs();
auto locAttrs = op.getAllPortLocs();
p << '(';
for (auto [i, port] : llvm::enumerate(modType.getPorts())) {
if (i > 0)
@ -449,3 +450,20 @@ void module_like_impl::printModuleSignatureNew(OpAsmPrinter &p,
p << ')';
}
/// Get a special name to use when printing the entry block arguments of the
/// region contained by an operation in this dialect.
void module_like_impl::getAsmBlockArgumentNamesImpl(
mlir::Region &region, OpAsmSetValueNameFn setNameFn) {
if (region.empty())
return;
// Assign port names to the bbargs.
auto module = cast<HWModuleOp>(region.getParentOp());
auto *block = &region.front();
for (size_t i = 0, e = block->getNumArguments(); i != e; ++i) {
auto name = module.getInputName(i);
// Let mlir deterministically convert names to valid identifiers
setNameFn(block->getArgument(i), name);
}
}

View File

@ -16,12 +16,14 @@
#include "circt/Dialect/HW/HWOps.h"
#include "circt/Dialect/HW/HWSymCache.h"
#include "circt/Dialect/HW/HWTypes.h"
#include "circt/Dialect/HW/ModuleImplementation.h"
#include "circt/Dialect/SV/SVAttributes.h"
#include "circt/Support/CustomDirectiveImpl.h"
#include "mlir/IR/Builders.h"
#include "mlir/IR/BuiltinTypes.h"
#include "mlir/IR/Matchers.h"
#include "mlir/IR/PatternMatch.h"
#include "mlir/Interfaces/FunctionImplementation.h"
#include "llvm/ADT/SmallString.h"
#include "llvm/ADT/StringExtras.h"
#include "llvm/ADT/TypeSwitch.h"
@ -2106,6 +2108,313 @@ ModportStructAttr ModportStructAttr::get(MLIRContext *context,
return get(context, ModportDirectionAttr::get(context, direction), signal);
}
//===----------------------------------------------------------------------===//
// FuncOp
//===----------------------------------------------------------------------===//
ParseResult FuncOp::parse(OpAsmParser &parser, OperationState &result) {
auto builder = parser.getBuilder();
// Parse visibility.
(void)mlir::impl::parseOptionalVisibilityKeyword(parser, result.attributes);
// Parse the name as a symbol.
StringAttr nameAttr;
if (parser.parseSymbolName(nameAttr, SymbolTable::getSymbolAttrName(),
result.attributes))
return failure();
SmallVector<hw::module_like_impl::PortParse> ports;
TypeAttr modType;
if (failed(
hw::module_like_impl::parseModuleSignature(parser, ports, modType)))
return failure();
result.addAttribute(FuncOp::getModuleTypeAttrName(result.name), modType);
// Convert the specified array of dictionary attrs (which may have null
// entries) to an ArrayAttr of dictionaries.
auto unknownLoc = builder.getUnknownLoc();
SmallVector<Attribute> attrs, inputLocs, outputLocs;
auto nonEmptyLocsFn = [unknownLoc](Attribute attr) {
return attr && cast<Location>(attr) != unknownLoc;
};
for (auto &port : ports) {
attrs.push_back(port.attrs ? port.attrs : builder.getDictionaryAttr({}));
auto loc = port.sourceLoc ? Location(*port.sourceLoc) : unknownLoc;
(port.direction == hw::PortInfo::Direction::Output ? outputLocs : inputLocs)
.push_back(loc);
}
result.addAttribute(FuncOp::getPerArgumentAttrsAttrName(result.name),
builder.getArrayAttr(attrs));
if (llvm::any_of(outputLocs, nonEmptyLocsFn))
result.addAttribute(FuncOp::getResultLocsAttrName(result.name),
builder.getArrayAttr(outputLocs));
// Parse the attribute dict.
if (failed(parser.parseOptionalAttrDictWithKeyword(result.attributes)))
return failure();
// Add the entry block arguments.
SmallVector<OpAsmParser::Argument, 4> entryArgs;
for (auto &port : ports)
if (port.direction != hw::ModulePort::Direction::Output)
entryArgs.push_back(port);
// Parse the optional function body. The printer will not print the body if
// its empty, so disallow parsing of empty body in the parser.
auto *body = result.addRegion();
llvm::SMLoc loc = parser.getCurrentLocation();
mlir::OptionalParseResult parseResult =
parser.parseOptionalRegion(*body, entryArgs,
/*enableNameShadowing=*/false);
if (parseResult.has_value()) {
if (failed(*parseResult))
return failure();
// Function body was parsed, make sure its not empty.
if (body->empty())
return parser.emitError(loc, "expected non-empty function body");
} else {
if (llvm::any_of(inputLocs, nonEmptyLocsFn))
result.addAttribute(FuncOp::getInputLocsAttrName(result.name),
builder.getArrayAttr(inputLocs));
}
return success();
}
void FuncOp::getAsmBlockArgumentNames(mlir::Region &region,
mlir::OpAsmSetValueNameFn setNameFn) {
if (region.empty())
return;
// Assign port names to the bbargs.
auto func = cast<FuncOp>(region.getParentOp());
auto *block = &region.front();
auto names = func.getModuleType().getInputNames();
for (size_t i = 0, e = block->getNumArguments(); i != e; ++i) {
// Let mlir deterministically convert names to valid identifiers
setNameFn(block->getArgument(i), cast<StringAttr>(names[i]));
}
}
Type FuncOp::getExplicitlyReturnedType() {
if (!getPerArgumentAttrs() || getNumOutputs() == 0)
return {};
// Check if the last port is used as an explicit return.
auto lastArgument = getModuleType().getPorts().back();
auto lastArgumentAttr = dyn_cast<DictionaryAttr>(
getPerArgumentAttrsAttr()[getPerArgumentAttrsAttr().size() - 1]);
if (lastArgument.dir == hw::ModulePort::Output && lastArgumentAttr &&
lastArgumentAttr.getAs<UnitAttr>(getExplicitlyReturnedAttrName()))
return lastArgument.type;
return {};
}
ArrayRef<Attribute> FuncOp::getAllPortAttrs() {
if (getPerArgumentAttrs())
return getPerArgumentAttrs()->getValue();
return {};
}
void FuncOp::setAllPortAttrs(ArrayRef<Attribute> attrs) {
setPerArgumentAttrsAttr(ArrayAttr::get(getContext(), attrs));
}
void FuncOp::removeAllPortAttrs() { setPerArgumentAttrsAttr({}); }
SmallVector<Location> FuncOp::getAllPortLocs() {
SmallVector<Location> portLocs;
portLocs.reserve(getNumPorts());
auto resultLocs = getResultLocsAttr();
unsigned inputCount = 0;
auto modType = getModuleType();
auto unknownLoc = UnknownLoc::get(getContext());
auto *body = getBodyBlock();
auto inputLocs = getInputLocsAttr();
for (unsigned i = 0, e = getNumPorts(); i < e; ++i) {
if (modType.isOutput(i)) {
auto loc = resultLocs
? cast<Location>(
resultLocs.getValue()[portLocs.size() - inputCount])
: unknownLoc;
portLocs.push_back(loc);
} else {
auto loc = body ? body->getArgument(inputCount).getLoc()
: (inputLocs ? cast<Location>(inputLocs[inputCount])
: unknownLoc);
portLocs.push_back(loc);
++inputCount;
}
}
return portLocs;
}
void FuncOp::setAllPortLocsAttrs(llvm::ArrayRef<mlir::Attribute> locs) {
SmallVector<Attribute> resultLocs, inputLocs;
unsigned inputCount = 0;
auto modType = getModuleType();
auto *body = getBodyBlock();
for (unsigned i = 0, e = getNumPorts(); i < e; ++i) {
if (modType.isOutput(i))
resultLocs.push_back(locs[i]);
else if (body)
body->getArgument(inputCount++).setLoc(cast<Location>(locs[i]));
else // Need to store locations in an attribute if declaration.
inputLocs.push_back(locs[i]);
}
setResultLocsAttr(ArrayAttr::get(getContext(), resultLocs));
if (!body)
setInputLocsAttr(ArrayAttr::get(getContext(), inputLocs));
}
SmallVector<hw::PortInfo> FuncOp::getPortList() { return getPortList(false); }
hw::PortInfo FuncOp::getPort(size_t idx) {
auto modTy = getHWModuleType();
auto emptyDict = DictionaryAttr::get(getContext());
LocationAttr loc = getPortLoc(idx);
DictionaryAttr attrs = dyn_cast_or_null<DictionaryAttr>(getPortAttrs(idx));
if (!attrs)
attrs = emptyDict;
return {modTy.getPorts()[idx],
modTy.isOutput(idx) ? modTy.getOutputIdForPortId(idx)
: modTy.getInputIdForPortId(idx),
attrs, loc};
}
SmallVector<hw::PortInfo> FuncOp::getPortList(bool excludeExplicitReturn) {
auto modTy = getModuleType();
auto emptyDict = DictionaryAttr::get(getContext());
auto skipLastArgument = getExplicitlyReturnedType() && excludeExplicitReturn;
SmallVector<hw::PortInfo> retval;
auto portAttr = getAllPortLocs();
for (unsigned i = 0, e = skipLastArgument ? modTy.getNumPorts() - 1
: modTy.getNumPorts();
i < e; ++i) {
DictionaryAttr attrs = emptyDict;
if (auto perArgumentAttr = getPerArgumentAttrs())
if (auto argumentAttr =
dyn_cast_or_null<DictionaryAttr>((*perArgumentAttr)[i]))
attrs = argumentAttr;
retval.push_back({modTy.getPorts()[i],
modTy.isOutput(i) ? modTy.getOutputIdForPortId(i)
: modTy.getInputIdForPortId(i),
attrs, portAttr[i]});
}
return retval;
}
void FuncOp::print(OpAsmPrinter &p) {
FuncOp op = *this;
// Print the operation and the function name.
auto funcName =
op->getAttrOfType<StringAttr>(SymbolTable::getSymbolAttrName())
.getValue();
p << ' ';
StringRef visibilityAttrName = SymbolTable::getVisibilityAttrName();
if (auto visibility = op->getAttrOfType<StringAttr>(visibilityAttrName))
p << visibility.getValue() << ' ';
p.printSymbolName(funcName);
hw::module_like_impl::printModuleSignatureNew(
p, op.getBody(), op.getModuleType(),
op.getPerArgumentAttrsAttr()
? ArrayRef<Attribute>(op.getPerArgumentAttrsAttr().getValue())
: ArrayRef<Attribute>{},
getAllPortLocs());
mlir::function_interface_impl::printFunctionAttributes(
p, op,
{visibilityAttrName, getModuleTypeAttrName(),
getPerArgumentAttrsAttrName(), getInputLocsAttrName(),
getResultLocsAttrName()});
// Print the body if this is not an external function.
Region &body = op->getRegion(0);
if (!body.empty()) {
p << ' ';
p.printRegion(body, /*printEntryBlockArgs=*/false,
/*printBlockTerminators=*/true);
}
}
//===----------------------------------------------------------------------===//
// ReturnOp
//===----------------------------------------------------------------------===//
LogicalResult ReturnOp::verify() {
auto func = getParentOp<sv::FuncOp>();
auto funcResults = func.getResultTypes();
auto returnedValues = getOperands();
if (funcResults.size() != returnedValues.size())
return emitOpError("must have same number of operands as region results.");
// Check that the types of our operands and the region's results match.
for (size_t i = 0, e = funcResults.size(); i < e; ++i) {
if (funcResults[i] != returnedValues[i].getType()) {
emitOpError("output types must match function. In "
"operand ")
<< i << ", expected " << funcResults[i] << ", but got "
<< returnedValues[i].getType() << ".";
return failure();
}
}
return success();
}
//===----------------------------------------------------------------------===//
// Call Ops
//===----------------------------------------------------------------------===//
static Value
getExplicitlyReturnedValueImpl(sv::FuncOp op,
mlir::Operation::result_range results) {
if (!op.getExplicitlyReturnedType())
return {};
return results.back();
}
Value FuncCallOp::getExplicitlyReturnedValue(sv::FuncOp op) {
return getExplicitlyReturnedValueImpl(op, getResults());
}
Value FuncCallProceduralOp::getExplicitlyReturnedValue(sv::FuncOp op) {
return getExplicitlyReturnedValueImpl(op, getResults());
}
LogicalResult
FuncCallProceduralOp::verifySymbolUses(SymbolTableCollection &symbolTable) {
auto referencedOp = dyn_cast_or_null<sv::FuncOp>(
symbolTable.lookupNearestSymbolFrom(*this, getCalleeAttr()));
if (!referencedOp)
return emitError("cannot find function declaration '")
<< getCallee() << "'";
return success();
}
LogicalResult FuncCallOp::verifySymbolUses(SymbolTableCollection &symbolTable) {
auto referencedOp = dyn_cast_or_null<sv::FuncOp>(
symbolTable.lookupNearestSymbolFrom(*this, getCalleeAttr()));
if (!referencedOp)
return emitError("cannot find function declaration '")
<< getCallee() << "'";
// Non-procedural call cannot have output arguments.
if (referencedOp.getNumOutputs() != 1 ||
!referencedOp.getExplicitlyReturnedType()) {
auto diag = emitError()
<< "function called in a non-procedural region must "
"return a single result";
diag.attachNote(referencedOp.getLoc()) << "doesn't satisfy the constraint";
return failure();
}
return success();
}
//===----------------------------------------------------------------------===//
// TableGen generated logic.
//===----------------------------------------------------------------------===//

View File

@ -12,6 +12,14 @@ hw.module.extern @test4(in %input: i7, out output: i7)
hw.module.extern @test5(in %input: i7 {hw.arg = "arg"}, out output: i7 {hw.res = "res"})
hw.module.extern @test6(in %input: i7 loc("arg"), out output: i7 loc("res"))
hw.module.extern @test7(in %input: i7 {hw.arg = "arg"} loc("arg"), out output: i7 {hw.res = "res"} loc("res"))
sv.func @test8(in %input: i7, out output: i7) { sv.return %input : i7 }
sv.func @test9(in %input: i7 {hw.arg = "arg"}, out output: i7 {hw.res = "res"}) { sv.return %input : i7 }
sv.func @test10(in %input: i7 loc("arg"), out output: i7 loc("res")) { sv.return %input : i7 }
sv.func @test11(in %input: i7 {hw.arg = "arg"} loc("arg"), out output: i7 {hw.res = "res"} loc("res")) { sv.return %input : i7 }
sv.func private @test12(in %input: i7, out output: i7)
sv.func private @test13(in %input: i7 {hw.arg = "arg"}, out output: i7 {hw.res = "res"})
sv.func private @test14(in %input: i7 loc("arg"), out output: i7 loc("res"))
sv.func private @test15(in %input: i7 {hw.arg = "arg"} loc("arg"), out output: i7 {hw.res = "res"} loc("res"))
// BASIC: hw.module @test0(in %input : i7, out output : i7)
// BASIC: hw.module @test1(in %input : i7 {hw.arg = "arg"}, out output : i7 {hw.res = "res"})
@ -21,6 +29,14 @@ hw.module.extern @test7(in %input: i7 {hw.arg = "arg"} loc("arg"), out output: i
// BASIC: hw.module.extern @test5(in %input : i7 {hw.arg = "arg"}, out output : i7 {hw.res = "res"})
// BASIC: hw.module.extern @test6(in %input : i7, out output : i7)
// BASIC: hw.module.extern @test7(in %input : i7 {hw.arg = "arg"}, out output : i7 {hw.res = "res"})
// BASIC: sv.func @test8(in %input : i7, out output : i7)
// BASIC: sv.func @test9(in %input : i7 {hw.arg = "arg"}, out output : i7 {hw.res = "res"})
// BASIC: sv.func @test10(in %input : i7, out output : i7)
// BASIC: sv.func @test11(in %input : i7 {hw.arg = "arg"}, out output : i7 {hw.res = "res"})
// BASIC: sv.func private @test12(in %input : i7, out output : i7)
// BASIC: sv.func private @test13(in %input : i7 {hw.arg = "arg"}, out output : i7 {hw.res = "res"})
// BASIC: sv.func private @test14(in %input : i7, out output : i7)
// BASIC: sv.func private @test15(in %input : i7 {hw.arg = "arg"}, out output : i7 {hw.res = "res"})
// DEBUG: hw.module @test0(in %input : i7 loc({{.+}}), out output : i7 loc({{.+}}))
// DEBUG: hw.module @test1(in %input : i7 {hw.arg = "arg"} loc({{.+}}), out output : i7 {hw.res = "res"} loc({{.+}}))
@ -30,3 +46,11 @@ hw.module.extern @test7(in %input: i7 {hw.arg = "arg"} loc("arg"), out output: i
// DEBUG: hw.module.extern @test5(in %input : i7 {hw.arg = "arg"} loc({{.+}}), out output : i7 {hw.res = "res"} loc({{.+}}))
// DEBUG: hw.module.extern @test6(in %input : i7 loc("arg"), out output : i7 loc("res"))
// DEBUG: hw.module.extern @test7(in %input : i7 {hw.arg = "arg"} loc("arg"), out output : i7 {hw.res = "res"} loc("res"))
// DEBUG: sv.func @test8(in %input : i7 loc({{.+}}), out output : i7 loc({{.+}}))
// DEBUG: sv.func @test9(in %input : i7 {hw.arg = "arg"} loc({{.+}}), out output : i7 {hw.res = "res"} loc({{.+}}))
// DEBUG: sv.func @test10(in %input : i7 loc("arg"), out output : i7 loc("res"))
// DEBUG: sv.func @test11(in %input : i7 {hw.arg = "arg"} loc("arg"), out output : i7 {hw.res = "res"} loc("res"))
// DEBUG: sv.func private @test12(in %input : i7 loc({{.+}}), out output : i7 loc({{.+}}))
// DEBUG: sv.func private @test13(in %input : i7 {hw.arg = "arg"} loc({{.+}}), out output : i7 {hw.res = "res"} loc({{.+}}))
// DEBUG: sv.func private @test14(in %input : i7 loc("arg"), out output : i7 loc("res"))
// DEBUG: sv.func private @test15(in %input : i7 {hw.arg = "arg"} loc("arg"), out output : i7 {hw.res = "res"} loc("res"))

View File

@ -389,3 +389,22 @@ hw.module @XMRRefOp() {
// CHECK: %1 = sv.xmr.ref @ref2 ".x.y.z[42]" : !hw.inout<i8>
%1 = sv.xmr.ref @ref2 ".x.y.z[42]" : !hw.inout<i8>
}
// Functions.
// CHECK-LABEL: sv.func private @function_declare(in %in_0 : i2, in %in_1 : i2, out out_0 : i1, in %in_2 : !hw.array<2xi2>)
sv.func private @function_declare(in %in_0 : i2, in %in_1 : i2, out out_0 : i1, in %in_2 : !hw.array<2xi2>)
// CHECK-LABEL: sv.func private @function_define(in %in_0 : i2, in %in_1 : i2, out out_0 : i1, in %in_2 : !hw.array<2xi2>)
sv.func private @function_define(in %in_0 : i2, in %in_1 : i2, out out_0 : i1, in %in_2 : !hw.array<2xi2>) attributes {test = "foo"} {
%0 = comb.icmp eq %in_0, %in_1: i2
// CHECK: sv.return %{{.+}} : i1
sv.return %0 : i1
}
// CHECK-LABEL: sv.func @recurse(in %n : i32, out out : i32) {
// CHECK: %0 = sv.func.call.procedural @recurse(%n) : (i32) -> i32
// CHECK-NEXT: sv.return %0
sv.func @recurse(in %n : i32, out out : i32) {
%v = sv.func.call.procedural @recurse(%n) : (i32) -> i32
sv.return %v : i32
}

View File

@ -238,3 +238,37 @@ hw.module @NoMessage(in %clock: i1, in %value : i4) {
"sv.assert"(%clock, %value) { defer = 0 : i32 } : (i1, i4) -> ()
}
}
// -----
sv.func private @function() {
%0 = hw.constant true
// expected-error @below {{'sv.return' op must have same number of operands as region results}}
sv.return %0 : i1
}
// -----
sv.func private @function(out out: i2) {
%0 = hw.constant true
// expected-error @below {{'sv.return' op output types must match function. In operand 0, expected 'i2', but got 'i1'}}
sv.return %0 : i1
}
// -----
hw.module private @module(out out: i2) {
%0 = hw.constant true
// expected-error @below {{'sv.return' op expects parent op 'sv.func'}}
sv.return %0 : i1
}
// -----
// expected-note @below {{doesn't satisfy the constraint}}
sv.func private @func(out out: i1)
hw.module private @call(){
// expected-error @below {{function called in a non-procedural region must return a single result}}
%0 = sv.func.call @func() : () -> (i1)
}