Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[FIRRTL] Add Inline Formal Test ops #7374

Merged
merged 38 commits into from
Aug 14, 2024
Merged
Show file tree
Hide file tree
Changes from 11 commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
c494484
added firrtl formal op
dobios Jul 22, 2024
bb42bc7
updated fir parser and emitter for new op
dobios Jul 23, 2024
6c5692e
added region parsing to formal parser
dobios Jul 23, 2024
5b35d94
Merge branch 'main' into dev/dobios/firrtl-inline-formal
dobios Jul 23, 2024
3c0b20f
reference variable
dobios Jul 23, 2024
09cf5d8
added symbolic value op to firrtl
dobios Jul 23, 2024
84799fa
typo in test
dobios Jul 23, 2024
ef97ef1
updated parsing test
dobios Jul 23, 2024
f8cc369
allow for instances in the formalOp
dobios Jul 23, 2024
9bffb6e
wip fixing symbolic op
dobios Jul 24, 2024
6a0a7d9
removed namekind from symbolic_value
dobios Jul 24, 2024
3188d4e
removed custom builders
dobios Jul 24, 2024
08c4283
Update lib/Dialect/FIRRTL/Import/FIRParser.cpp
dobios Jul 24, 2024
4a6d635
WIP deferred formal body parsing
dobios Jul 24, 2024
a5b6f2a
Fixed copy-pasta in deferred parsing
dobios Jul 24, 2024
3e4e3f9
Implement InstanceGraphModuleOpInterface
dobios Jul 24, 2024
3bbe281
Declare interface ops
dobios Jul 25, 2024
88f3599
Merge branch 'main' into dev/dobios/firrtl-inline-formal
dobios Jul 25, 2024
aa867f2
implemented FIRRTLModuleLike interfaces for FormalOp
dobios Jul 26, 2024
b5d4fbc
WIP update FModuleOp passes to support all FModuleLike ops
dobios Jul 26, 2024
77d36f5
converted EliminateWires to interface pass
dobios Jul 26, 2024
8de018a
converted InferReadWrite to an interface pass on supported FModuleLike
dobios Jul 26, 2024
852a624
Converted all FModuleOp passes to FModuleLike passes
dobios Jul 27, 2024
d238319
Revert "Converted all FModuleOp passes to FModuleLike passes"
dobios Jul 30, 2024
09ca1d8
Revert "converted InferReadWrite to an interface pass on supported FM…
dobios Jul 30, 2024
33c04c8
Revert "converted EliminateWires to interface pass"
dobios Jul 30, 2024
d42c306
Revert "WIP update FModuleOp passes to support all FModuleLike ops"
dobios Jul 30, 2024
8d869ca
Merge branch 'main' into dev/dobios/firrtl-inline-formal
dobios Aug 1, 2024
eaf91cf
WIP updating formalO
dobios Aug 1, 2024
aaf16ad
Merge branch 'main' into dev/dobios/firrtl-inline-formal
dobios Aug 6, 2024
1abc1a4
Updating FormalOp
dobios Aug 6, 2024
5f30d59
added method declarations
dobios Aug 7, 2024
3fb9a2a
removed defered parsing for formal body
dobios Aug 7, 2024
134dca3
updated symbol checking to only hapen in verifier
dobios Aug 8, 2024
a1fefeb
constrained formalOp to only accept FModuleOps
dobios Aug 9, 2024
f1d8719
implemented Symbol interface
dobios Aug 12, 2024
81050da
Merge branch 'main' into dev/dobios/firrtl-inline-formal
dobios Aug 12, 2024
a251019
fixed Symbol implementation
dobios Aug 13, 2024
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
43 changes: 41 additions & 2 deletions include/circt/Dialect/FIRRTL/FIRRTLDeclarations.td
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,8 @@ class ReferableDeclOp<string mnemonic, list<Trait> traits = []> :
/// located in a hardware-creating context, such as the body of a module.
class HardwareDeclOp<string mnemonic, list <Trait> traits = []> :
ReferableDeclOp<mnemonic, traits # [
ParentOneOf<[
"firrtl::FModuleOp", "firrtl::LayerBlockOp",
ParentOneOf<[
"firrtl::FormalOp", "firrtl::FModuleOp", "firrtl::LayerBlockOp",
"firrtl::WhenOp", "firrtl::MatchOp", "sv::IfDefOp"]>]> {}

def InstanceOp : HardwareDeclOp<"instance", [
Expand Down Expand Up @@ -336,6 +336,45 @@ def MemOp : HardwareDeclOp<"mem", [DeclareOpInterfaceMethods<CombDataflow>]> {
}];
}

def SymbolicOp : FIRRTLOp<"symbolic", [
HasParent<"firrtl::FormalOp">, HasCustomSSAName
]> {
let summary = "Produce a symbolic value (non-deterministic value)";
let description = [{
The symbolic operation produces a symbolic value of UInt Type. This
is solely used in the context of formal verification in order to reason
about module instance inputs in a symbolic manner.
Example:
```mlir
%foo.in = firrtl.symbolic : t1
%foo.out = firrtl.instance "foo" @Foo(in: %foo.in : t1) -> (t1)
%cond = firrtl.geq %foo.in, foo.out : (t1, t1) -> (t1)
firrtl.int.verif.assert %cond : t1
```
}];

let arguments = (ins StrAttr:$name);
let results = (outs UIntType:$result);

let skipDefaultBuilders = true;

let builders = [
OpBuilder<(ins "::mlir::Type":$elementType,
"::mlir::StringAttr":$name), [{
$_state.addAttribute(getNameAttrName($_state.name), name);
$_state.addTypes(elementType);
}]>,
OpBuilder<(ins "::mlir::Type":$elementType,
"::llvm::StringRef":$name), [{
return build($_builder, $_state, elementType, $_builder.getStringAttr(name));
}]>
];
dobios marked this conversation as resolved.
Show resolved Hide resolved

let assemblyFormat = [{
custom<ImplicitSSAName>(attr-dict) `:`qualified(type($result))
}];
}

def NodeOp : HardwareDeclOp<"node", [
AllTypesMatch<["input","result"]>,
DeclareOpInterfaceMethods<InferTypeOpInterface, ["inferReturnTypes"]>,
Expand Down
36 changes: 36 additions & 0 deletions include/circt/Dialect/FIRRTL/FIRRTLStructure.td
Original file line number Diff line number Diff line change
Expand Up @@ -448,6 +448,42 @@ def LayerOp : FIRRTLOp<
}];
}

def FormalOp : FIRRTLOp<"formal", [
IsolatedFromAbove,
Symbol,
NoTerminator,
HasParent<"firrtl::CircuitOp">
]> {
let summary = "A formal test definition.";
let description = [{
The `firrtl.formal` operation defines a formal verification problem in the same
context as the rest of the design. This problem is solved using bounded model
checking and should be given a bound k, which represents the number of cycles considered
during model checking.

Example:
```mlir

firrtl.formal @formal1(k=20) {
%bar, %out = firrtl.instance foo @Foo(in bar: !firrtl.uint<8>, out out: !firrtl.uint<8>)
%c42_8 = firrtl.constant 42 : !firrtl.uint<8>
firrtl.connect %bar, %c42_8: !firrtl.uint<8>, !firrtl.uint<8>
%c69_8 = firrtl.constant 69 : !firrtl.uint<8>
%cond = firrtl.eq %c69_8, %out : (!firrtl.uint<8>, !firrtl.uint<8>) -> !firrtl.uint<1>
firrtl.assert %cond
}
}
```
}];

let arguments = (ins SymbolNameAttr:$sym_name, UI64Attr:$k);
let results = (outs);
let regions = (region SizedRegion<1>:$body);
let assemblyFormat = [{
$sym_name `(` `k` `=` $k `)` attr-dict-with-keyword $body
}];
}

def OptionOp : FIRRTLOp<"option", [
IsolatedFromAbove,
Symbol,
Expand Down
3 changes: 2 additions & 1 deletion include/circt/Dialect/FIRRTL/FIRRTLVisitors.h
Original file line number Diff line number Diff line change
Expand Up @@ -304,7 +304,7 @@ class DeclVisitor {
auto *thisCast = static_cast<ConcreteType *>(this);
return TypeSwitch<Operation *, ResultType>(op)
.template Case<InstanceOp, InstanceChoiceOp, ObjectOp, MemOp, NodeOp,
RegOp, RegResetOp, WireOp, VerbatimWireOp>(
RegOp, RegResetOp, SymbolicOp, WireOp, VerbatimWireOp>(
[&](auto opNode) -> ResultType {
return thisCast->visitDecl(opNode, args...);
})
Expand Down Expand Up @@ -337,6 +337,7 @@ class DeclVisitor {
HANDLE(NodeOp);
HANDLE(RegOp);
HANDLE(RegResetOp);
HANDLE(SymbolicOp);
HANDLE(WireOp);
HANDLE(VerbatimWireOp);
#undef HANDLE
Expand Down
37 changes: 35 additions & 2 deletions lib/Dialect/FIRRTL/Export/FIREmitter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ struct Emitter {
void emitModuleParameters(Operation *op, ArrayAttr parameters);
void emitDeclaration(LayerOp op);
void emitDeclaration(OptionOp op);
void emitDeclaration(FormalOp op);
void emitEnabledLayers(ArrayRef<Attribute> layers);

void emitParamAssign(ParamDeclAttr param, Operation *op,
Expand All @@ -70,6 +71,7 @@ struct Emitter {
void emitStatementsInBlock(Block &block);
void emitStatement(WhenOp op);
void emitStatement(WireOp op);
void emitStatement(SymbolicOp op);
void emitStatement(RegOp op);
void emitStatement(RegResetOp op);
void emitStatement(NodeOp op);
Expand Down Expand Up @@ -406,6 +408,7 @@ void Emitter::emitCircuit(CircuitOp op) {
})
.Case<LayerOp>([&](auto op) { emitDeclaration(op); })
.Case<OptionOp>([&](auto op) { emitDeclaration(op); })
.Case<FormalOp>([&](auto op) { emitDeclaration(op); })
.Default([&](auto op) {
emitOpError(op, "not supported for emission inside circuit");
});
Expand Down Expand Up @@ -623,6 +626,26 @@ void Emitter::emitDeclaration(OptionOp op) {
ps << PP::newline << PP::newline;
}

/// Emit a formal test definition.
void Emitter::emitDeclaration(FormalOp op) {
startStatement();
ps << "formal " << PPExtString(op.getSymName()) << ", "
<< "k = ";
ps.addAsString(op.getK());

if (auto outputFile = op->getAttrOfType<hw::OutputFileAttr>("output_file")) {
ps << ", ";
ps.writeQuotedEscaped(outputFile.getFilename().getValue());
}

ps << " : ";
emitLocationAndNewLine(op);
ps.scopedBox(PP::bbox2, [&]() {
// Emit the test's body
emitStatementsInBlock(op.getBody().front());
});
}

/// Check if an operation is inlined into the emission of their users. For
/// example, subfields are always inlined.
static bool isEmittedInline(Operation *op) {
Expand All @@ -643,8 +666,8 @@ void Emitter::emitStatementsInBlock(Block &block) {
if (isEmittedInline(&bodyOp))
continue;
TypeSwitch<Operation *>(&bodyOp)
.Case<WhenOp, WireOp, RegOp, RegResetOp, NodeOp, StopOp, SkipOp,
PrintFOp, AssertOp, AssumeOp, CoverOp, ConnectOp,
.Case<WhenOp, WireOp, SymbolicOp, RegOp, RegResetOp, NodeOp, StopOp,
SkipOp, PrintFOp, AssertOp, AssumeOp, CoverOp, ConnectOp,
MatchingConnectOp, PropAssignOp, InstanceOp, InstanceChoiceOp,
AttachOp, MemOp, InvalidValueOp, SeqMemOp, CombMemOp,
MemoryPortOp, MemoryDebugPortOp, MemoryPortAccessOp, RefDefineOp,
Expand Down Expand Up @@ -699,6 +722,16 @@ void Emitter::emitStatement(WireOp op) {
emitLocationAndNewLine(op);
}

void Emitter::emitStatement(SymbolicOp op) {
auto legalName = legalize(op.getNameAttr());
startStatement();
ps.scopedBox(PP::ibox2, [&]() {
ps << "symbolic " << PPExtString(legalName);
emitTypeWithColon(op.getResult().getType());
});
emitLocationAndNewLine(op);
}

void Emitter::emitStatement(RegOp op) {
auto legalName = legalize(op.getNameAttr());
addForceable(op, legalName);
Expand Down
4 changes: 4 additions & 0 deletions lib/Dialect/FIRRTL/FIRRTLOps.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3352,6 +3352,10 @@ void RegResetOp::getAsmResultNames(OpAsmSetValueNameFn setNameFn) {
return forceableAsmResultNames(*this, getName(), setNameFn);
}

void SymbolicOp::getAsmResultNames(OpAsmSetValueNameFn setNameFn) {
setNameFn(getResult(), getName());
}

void WireOp::getAsmResultNames(OpAsmSetValueNameFn setNameFn) {
return forceableAsmResultNames(*this, getName(), setNameFn);
}
Expand Down
73 changes: 73 additions & 0 deletions lib/Dialect/FIRRTL/Import/FIRParser.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1747,6 +1747,7 @@ struct FIRStmtParser : public FIRParser {
ParseResult parseRWProbe(Value &result);
ParseResult parseLeadingExpStmt(Value lhs);
ParseResult parseConnect();
ParseResult parseSymbolic();
ParseResult parseInvalidate();
ParseResult parseLayerBlockOrGroup(unsigned indent);

Expand Down Expand Up @@ -2666,6 +2667,8 @@ ParseResult FIRStmtParser::parseSimpleStmtImpl(unsigned stmtIndent) {
return parseNode();
case FIRToken::kw_wire:
return parseWire();
case FIRToken::kw_symbolic:
return parseSymbolic();
case FIRToken::kw_reg:
return parseRegister(stmtIndent);
case FIRToken::kw_regreset:
Expand Down Expand Up @@ -3797,6 +3800,28 @@ ParseResult FIRStmtParser::parseConnect() {
return success();
}

ParseResult FIRStmtParser::parseSymbolic() {
auto startTok = consumeToken(FIRToken::kw_symbolic);

// If this was actually the start of a connect or something else handle
// that.
if (auto isExpr = parseExpWithLeadingKeyword(startTok))
return *isExpr;
dobios marked this conversation as resolved.
Show resolved Hide resolved

StringRef id;
FIRRTLType type;
if (parseId(id, "expected symbolic value name") ||
parseToken(FIRToken::colon, "expected ':' in symbolic value") ||
parseType(type, "expected symbolic value type") || parseOptionalInfo())
return failure();

locationProcessor.setLoc(startTok.getLoc());

auto result = builder.create<SymbolicOp>(type, id);
return moduleContext.addSymbolEntry(id, result.getResult(),
startTok.getLoc());
}

/// propassign ::= 'propassign' expr expr
ParseResult FIRStmtParser::parsePropAssign() {
auto startTok = consumeToken(FIRToken::kw_propassign);
Expand Down Expand Up @@ -4585,6 +4610,7 @@ struct FIRCircuitParser : public FIRParser {
ParseResult parseExtModule(CircuitOp circuit, unsigned indent);
ParseResult parseIntModule(CircuitOp circuit, unsigned indent);
ParseResult parseModule(CircuitOp circuit, bool isPublic, unsigned indent);
ParseResult parseFormal(CircuitOp circuit, unsigned indent);

ParseResult parseLayerName(SymbolRefAttr &result);
ParseResult parseOptionalEnabledLayers(ArrayAttr &result);
Expand Down Expand Up @@ -4890,6 +4916,7 @@ ParseResult FIRCircuitParser::skipToModuleEnd(unsigned indent) {
case FIRToken::kw_extclass:
case FIRToken::kw_extmodule:
case FIRToken::kw_intmodule:
case FIRToken::kw_formal:
case FIRToken::kw_module:
case FIRToken::kw_public:
case FIRToken::kw_layer:
Expand Down Expand Up @@ -5153,6 +5180,47 @@ ParseResult FIRCircuitParser::parseModule(CircuitOp circuit, bool isPublic,
return success();
}

ParseResult FIRCircuitParser::parseFormal(CircuitOp circuit, unsigned indent) {
consumeToken(FIRToken::kw_formal);
StringRef id, kSpelling;
int64_t k = -1;
LocWithInfo info(getToken().getLoc(), this);

// Parse the formal operation
if (parseId(id, "expected a formal test name") ||
parseToken(FIRToken::comma, "expected ','") ||
parseGetSpelling(kSpelling) ||
parseToken(FIRToken::identifier, "expected parameter 'k' after ','") ||
parseToken(FIRToken::equal, "expected '=' after 'k'") ||
parseIntLit(k, "expected integer in k specification") ||
parseToken(FIRToken::colon,
"expected ':' after option group definition") ||
info.parseOptionalInfo())
return failure();

// Check that the parameter is valid
if (kSpelling != "k" || k <= 0)
return emitError("Invalid parameter given to formal test: ") << kSpelling,
failure();

// Build out the firrtl mlir op
auto builder = circuit.getBodyBuilder();
auto formal = builder.create<firrtl::FormalOp>(info.getLoc(), id, k);
auto &body = formal.getBody().emplaceBlock();

// Create the statement parser
hw::InnerSymbolNamespace ns(circuit);
dobios marked this conversation as resolved.
Show resolved Hide resolved
SymbolTable circuitSymTbl(circuit);
dobios marked this conversation as resolved.
Show resolved Hide resolved
FIRModuleContext context(getConstants(), getLexer(), version);
FIRStmtParser stmtParser(body, context, ns, circuitSymTbl, version);

// If we've reached a new indentation, parse the internal region
if (getIndentation() > indent)
if (stmtParser.parseSimpleStmtBlock(indent))
return failure();
return success();
}

ParseResult FIRCircuitParser::parseToplevelDefinition(CircuitOp circuit,
unsigned indent) {
switch (getToken().getKind()) {
Expand All @@ -5167,6 +5235,10 @@ ParseResult FIRCircuitParser::parseToplevelDefinition(CircuitOp circuit,
return parseExtClass(circuit, indent);
case FIRToken::kw_extmodule:
return parseExtModule(circuit, indent);
case FIRToken::kw_formal:
if (requireFeature({4, 0, 0}, "inline formal tests"))
return failure();
return parseFormal(circuit, indent);
case FIRToken::kw_intmodule:
if (removedFeature({4, 0, 0}, "intrinsic modules"))
return failure();
Expand Down Expand Up @@ -5514,6 +5586,7 @@ ParseResult FIRCircuitParser::parseCircuit(
case FIRToken::kw_extmodule:
case FIRToken::kw_intmodule:
case FIRToken::kw_layer:
case FIRToken::kw_formal:
case FIRToken::kw_module:
case FIRToken::kw_option:
case FIRToken::kw_public:
Expand Down
2 changes: 2 additions & 0 deletions lib/Dialect/FIRRTL/Import/FIRTokenKinds.def
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,7 @@ TOK_KEYWORD(extclass)
TOK_KEYWORD(extmodule)
TOK_KEYWORD(false)
TOK_KEYWORD(flip)
TOK_KEYWORD(formal)
TOK_KEYWORD(group)
TOK_KEYWORD(infer)
TOK_KEYWORD(input)
Expand Down Expand Up @@ -151,6 +152,7 @@ TOK_KEYWORD(regreset)
TOK_KEYWORD(reset)
TOK_KEYWORD(skip)
TOK_KEYWORD(smem)
TOK_KEYWORD(symbolic)
TOK_KEYWORD(true)
TOK_KEYWORD(type)
TOK_KEYWORD(undefined)
Expand Down
4 changes: 2 additions & 2 deletions test/Dialect/FIRRTL/errors.mlir
Original file line number Diff line number Diff line change
Expand Up @@ -279,7 +279,7 @@ firrtl.circuit "Foo" {

firrtl.circuit "Foo" {
firrtl.extmodule @Foo()
// expected-error @+1 {{'firrtl.instance' op expects parent op to be one of 'firrtl.module, firrtl.layerblock, firrtl.when, firrtl.match, sv.ifdef'}}
// expected-error @+1 {{'firrtl.instance' op expects parent op to be one of 'firrtl.formal, firrtl.module, firrtl.layerblock, firrtl.when, firrtl.match, sv.ifdef'}}
firrtl.instance "" @Foo()
}

Expand Down Expand Up @@ -1829,7 +1829,7 @@ firrtl.circuit "ClassCannotHaveHardwarePorts" {
firrtl.circuit "ClassCannotHaveWires" {
firrtl.module @ClassCannotHaveWires() {}
firrtl.class @ClassWithWire() {
// expected-error @below {{'firrtl.wire' op expects parent op to be one of 'firrtl.module, firrtl.layerblock, firrtl.when, firrtl.match, sv.ifdef'}}
// expected-error @below {{'firrtl.wire' op expects parent op to be one of 'firrtl.formal, firrtl.module, firrtl.layerblock, firrtl.when, firrtl.match, sv.ifdef'}}
%w = firrtl.wire : !firrtl.uint<8>
}
}
Expand Down
Loading
Loading