Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 61 additions & 0 deletions include/circt/Dialect/SV/SVStatements.td
Original file line number Diff line number Diff line change
Expand Up @@ -1201,6 +1201,67 @@ def ReturnOp : SVOp<"return",
let hasVerifier = 1;
}

//===----------------------------------------------------------------------===//
// Macro instance operation
//===----------------------------------------------------------------------===//

def MacroInstanceOp : SVOp<"macro_instance", [
DeclareOpInterfaceMethods<SymbolUserOpInterface>,
DeclareOpInterfaceMethods<OpAsmOpInterface, ["getAsmResultNames"]>,
DeclareOpInterfaceMethods<InstanceGraphInstanceOpInterface>,
DeclareOpInterfaceMethods<InnerSymbol, ["getTargetResultIndex"]>,
NonProceduralOp
]> {
let summary = "Create an instance of a module selected by a macro";
let description = [{
This represents an instance of a module where the actual module to
instantiate is determined by a macro. The macro must be defined and
point to one of the candidate modules listed in the operation.

The candidate modules must all have compatible port signatures.

Example:
```mlir
%c = sv.macro_instance "foo" @Macro [@A, @B] (a: %a: i1, b: %b: i4) -> (c: i5)
```

This will emit Verilog like:
```verilog
`Macro foo (
.a(a),
.b(b),
.c(c)
);
```
}];

let arguments = (ins StrAttr:$instanceName,
FlatSymbolRefAttr:$macroName,
FlatSymbolRefArrayAttr:$moduleNames,
Variadic<AnyType>:$inputs,
StrArrayAttr:$argNames, StrArrayAttr:$resultNames,
OptionalAttr<InnerSymAttr>:$inner_sym);
let results = (outs Variadic<AnyType>:$results);

let hasCustomAssemblyFormat = 1;
let hasVerifier = 1;

let extraClassDeclaration = [{
/// Return the name of the first candidate module.
StringAttr getFirstModuleNameAttr() {
return llvm::cast<FlatSymbolRefAttr>(getModuleNamesAttr()[0]).getAttr();
}

/// Return the values for the port in port order.
void getValues(SmallVectorImpl<Value> &values, const hw::ModulePortInfo &mpi);

/// InstanceGraphInstanceOpInterface: Return the referenced module names.
ArrayAttr getReferencedModuleNamesAttr() {
return getModuleNamesAttr();
}
}];
}

//===----------------------------------------------------------------------===//
// SV output control.
//===----------------------------------------------------------------------===//
Expand Down
3 changes: 2 additions & 1 deletion include/circt/Dialect/SV/SVVisitors.h
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ class Visitor {
InterfaceOp, SVVerbatimSourceOp, InterfaceSignalOp,
InterfaceModportOp, InterfaceInstanceOp, GetModportOp,
AssignInterfaceSignalOp, ReadInterfaceSignalOp, MacroDeclOp,
MacroDefOp, FuncOp, FuncDPIImportOp,
MacroDefOp, MacroInstanceOp, FuncOp, FuncDPIImportOp,
// Verification statements.
AssertOp, AssumeOp, CoverOp, AssertConcurrentOp, AssumeConcurrentOp,
CoverConcurrentOp, AssertPropertyOp, AssumePropertyOp,
Expand Down Expand Up @@ -157,6 +157,7 @@ class Visitor {
HANDLE(ReadInterfaceSignalOp, Unhandled);
HANDLE(MacroDefOp, Unhandled);
HANDLE(MacroDeclOp, Unhandled);
HANDLE(MacroInstanceOp, Unhandled);
HANDLE(FuncDPIImportOp, Unhandled);
HANDLE(FuncOp, Unhandled);

Expand Down
45 changes: 44 additions & 1 deletion lib/Conversion/ExportVerilog/ExportVerilog.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4050,6 +4050,7 @@ class StmtEmitter : public EmitterBase,
LogicalResult visitSV(ReleaseOp op);
LogicalResult visitSV(AliasOp op);
LogicalResult visitSV(InterfaceInstanceOp op);
LogicalResult visitSV(MacroInstanceOp op);
LogicalResult emitOutputLikeOp(Operation *op, const ModulePortInfo &ports);
LogicalResult visitStmt(OutputOp op);

Expand Down Expand Up @@ -4373,7 +4374,7 @@ LogicalResult StmtEmitter::emitOutputLikeOp(Operation *op,
// directly when the instance is emitted.
// Keep synced with countStatements() and visitStmt(InstanceOp).
if (operand.hasOneUse() && operand.getDefiningOp() &&
isa<InstanceOp>(operand.getDefiningOp())) {
isa<InstanceOp, sv::MacroInstanceOp>(operand.getDefiningOp())) {
++operandIndex;
continue;
}
Expand Down Expand Up @@ -5575,6 +5576,48 @@ LogicalResult StmtEmitter::visitStmt(InstanceOp op) {
return success();
}

LogicalResult StmtEmitter::visitSV(MacroInstanceOp op) {
// Emit SV attributes.
emitSVAttributes(op);
startStatement();
ps.addCallback({op, true});

SmallPtrSet<Operation *, 8> ops;
ops.insert(op);

// Get the module port info from the first candidate module.
auto firstModuleName = op.getFirstModuleNameAttr();
auto *referencedModule = state.symbolCache.getDefinition(firstModuleName);
if (!referencedModule) {
emitOpError(op, "cannot find referenced module");
return failure();
}

auto referencedModuleLike = cast<hw::HWModuleLike>(referencedModule);

// Emit the macro reference as the module name.
// This emits: `MACRO_NAME instance_name (
auto *macroDeclOp = state.symbolCache.getDefinition(op.getMacroNameAttr().getAttr());
if (!macroDeclOp) {
emitOpError(op, "cannot find macro declaration");
return failure();
}

auto macroDecl = cast<MacroDeclOp>(macroDeclOp);
ps << "`" << macroDecl.getMacroIdentifier();
ps << PP::nbsp << PPExtString(op.getInstanceName());

// Emit the port list using the same logic as regular instances.
ModulePortInfo modPortInfo(referencedModuleLike.getPortList());
SmallVector<Value> instPortValues(modPortInfo.size());
op.getValues(instPortValues, modPortInfo);
emitInstancePortList(op, modPortInfo, instPortValues);

ps.addCallback({op, false});
emitLocationInfoAndNewLine(ops);
return success();
}

void StmtEmitter::emitInstancePortList(Operation *op,
ModulePortInfo &modPortInfo,
ArrayRef<Value> instPortValues) {
Expand Down
4 changes: 2 additions & 2 deletions lib/Conversion/ExportVerilog/PrepareForEmission.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -756,8 +756,8 @@ bool EmittedExpressionStateManager::shouldSpillWireBasedOnState(Operation &op) {
// to a wire.
if (op.hasOneUse()) {
auto *singleUser = *op.getUsers().begin();
if (isa<hw::OutputOp, sv::AssignOp, sv::BPAssignOp, hw::InstanceOp>(
singleUser))
if (isa<hw::OutputOp, sv::AssignOp, sv::BPAssignOp, hw::InstanceOp,
sv::MacroInstanceOp>(singleUser))
return false;

// If the single user is bitcast, we check the same property for the bitcast
Expand Down
145 changes: 145 additions & 0 deletions lib/Dialect/SV/SVOps.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2878,6 +2878,151 @@ LogicalResult CoverPropertyOp::verify() {
getLoc());
}

//===----------------------------------------------------------------------===//
// MacroInstanceOp
//===----------------------------------------------------------------------===//

void MacroInstanceOp::getAsmResultNames(OpAsmSetValueNameFn setNameFn) {
hw::instance_like_impl::getAsmResultNames(
setNameFn, getInstanceName(), getResultNames(), getResults());
}

std::optional<size_t> MacroInstanceOp::getTargetResultIndex() {
// Instance operations don't have a target result.
return std::nullopt;
}

void MacroInstanceOp::getValues(SmallVectorImpl<Value> &values,
const hw::ModulePortInfo &mpi) {
size_t inputPort = 0, resultPort = 0;
values.resize(mpi.size());
auto results = getResults();
auto inputs = getInputs();
for (auto [idx, port] : llvm::enumerate(mpi))
if (mpi.at(idx).isOutput())
values[idx] = results[resultPort++];
else
values[idx] = inputs[inputPort++];
}

LogicalResult
MacroInstanceOp::verifySymbolUses(SymbolTableCollection &symbolTable) {
// Verify that the macro exists.
auto macro = symbolTable.lookupNearestSymbolFrom<MacroDeclOp>(
*this, getMacroNameAttr());
if (!macro)
return emitError("cannot find macro declaration '")
<< getMacroName() << "'";

// Verify that all candidate modules exist and have compatible signatures.
for (Attribute name : getModuleNamesAttr()) {
if (failed(hw::instance_like_impl::verifyInstanceOfHWModule(
*this, cast<FlatSymbolRefAttr>(name), getInputs(), getResultTypes(),
getArgNames(), getResultNames(), ArrayAttr{}, symbolTable))) {
return failure();
}
}
return success();
}

ParseResult MacroInstanceOp::parse(OpAsmParser &parser,
OperationState &result) {
StringAttr instanceNameAttr;
FlatSymbolRefAttr macroNameAttr;
SmallVector<Attribute> moduleNames;
SmallVector<OpAsmParser::UnresolvedOperand, 4> inputsOperands;
SmallVector<Type, 1> inputsTypes, allResultTypes;
ArrayAttr argNames, resultNames;
auto noneType = parser.getBuilder().getType<NoneType>();

// Parse instance name.
if (parser.parseAttribute(instanceNameAttr, noneType, "instanceName",
result.attributes))
return failure();

// Parse optional inner_sym.
hw::InnerSymAttr innerSymAttr;
if (succeeded(parser.parseOptionalKeyword("sym"))) {
if (parser.parseCustomAttributeWithFallback(
innerSymAttr, noneType, hw::InnerSymbolTable::getInnerSymbolAttrName(),
result.attributes))
return failure();
}

// Parse macro name.
if (parser.parseAttribute(macroNameAttr, noneType, "macroName",
result.attributes))
return failure();

// Parse module names array [@moduleName1, @moduleName2, ...].
if (parser.parseLSquare())
return failure();

do {
FlatSymbolRefAttr moduleName;
if (parser.parseAttribute(moduleName))
return failure();
moduleNames.push_back(moduleName);
} while (succeeded(parser.parseOptionalComma()));
Comment on lines +2961 to +2966
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Manually parsing array feels not right.


if (parser.parseRSquare())
return failure();

result.addAttribute("moduleNames",
ArrayAttr::get(parser.getContext(), moduleNames));

// Parse input port list.
llvm::SMLoc inputsOperandsLoc = parser.getCurrentLocation();
if (parseInputPortList(parser, inputsOperands, inputsTypes, argNames) ||
parser.resolveOperands(inputsOperands, inputsTypes, inputsOperandsLoc,
result.operands))
return failure();

// Parse arrow and output port list.
if (parser.parseArrow() ||
parseOutputPortList(parser, allResultTypes, resultNames) ||
parser.parseOptionalAttrDict(result.attributes))
return failure();

result.addAttribute("argNames", argNames);
result.addAttribute("resultNames", resultNames);
result.addTypes(allResultTypes);
return success();
}

void MacroInstanceOp::print(OpAsmPrinter &p) {
p << ' ';
p.printAttributeWithoutType(getInstanceNameAttr());
if (auto attr = getInnerSymAttr()) {
p << " sym ";
attr.print(p);
}
p << ' ';
p.printAttributeWithoutType(getMacroNameAttr());
p << " [";
llvm::interleaveComma(getModuleNames(), p, [&](Attribute attr) {
p.printAttributeWithoutType(attr);
});
p << "]";
printInputPortList(p, *this, getInputs(), getInputs().getTypes(),
getArgNames());
p << " -> ";
printOutputPortList(p, *this, getResultTypes(), getResultNames());

p.printOptionalAttrDict(
(*this)->getAttrs(),
/*elidedAttrs=*/{"instanceName", hw::InnerSymbolTable::getInnerSymbolAttrName(),
"macroName", "moduleNames", "argNames", "resultNames"});
}

LogicalResult MacroInstanceOp::verify() {
// Verify that at least one candidate module is provided.
if (getModuleNames().empty())
return emitError("must have at least one candidate module");

return success();
}

//===----------------------------------------------------------------------===//
// TableGen generated logic.
//===----------------------------------------------------------------------===//
Expand Down
39 changes: 39 additions & 0 deletions test/Dialect/SV/macro_instance.mlir
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
// RUN: circt-opt %s | circt-opt | FileCheck %s
// RUN: circt-opt %s --export-verilog | FileCheck %s --check-prefix=VERILOG

// CHECK-LABEL: hw.module @ModuleA
hw.module @ModuleA(in %a: i1, in %b: i4, out c: i5) {
%0 = comb.concat %a, %b : i1, i4
hw.output %0 : i5
}

// CHECK-LABEL: hw.module @ModuleB
hw.module @ModuleB(in %a: i1, in %b: i4, out c: i5) {
%0 = comb.concat %a, %b : i1, i4
hw.output %0 : i5
}

// CHECK-LABEL: sv.macro.decl @WHICH_MODULE
sv.macro.decl @WHICH_MODULE

// CHECK-LABEL: hw.module @Top
hw.module @Top(in %a: i1, in %b: i4, out c: i5) {
// CHECK: %inst.c = sv.macro_instance "inst" @WHICH_MODULE [@ModuleA, @ModuleB](a: %a: i1, b: %b: i4) -> (c: i5)
%0 = sv.macro_instance "inst" @WHICH_MODULE [@ModuleA, @ModuleB] (a: %a: i1, b: %b: i4) -> (c: i5)
hw.output %0 : i5
}

// CHECK-LABEL: hw.module @TopWithSym
hw.module @TopWithSym(in %a: i1, in %b: i4, out c: i5) {
// CHECK: %inst2.c = sv.macro_instance "inst2" sym @inst_sym @WHICH_MODULE [@ModuleA, @ModuleB](a: %a: i1, b: %b: i4) -> (c: i5)
%0 = sv.macro_instance "inst2" sym @inst_sym @WHICH_MODULE [@ModuleA, @ModuleB] (a: %a: i1, b: %b: i4) -> (c: i5)
hw.output %0 : i5
}

// VERILOG-LABEL: module ModuleA
// VERILOG-LABEL: module ModuleB
// VERILOG-LABEL: module Top
// VERILOG: `WHICH_MODULE inst (
// VERILOG-LABEL: module TopWithSym
// VERILOG: `WHICH_MODULE inst2 (

Loading