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

[circt-lec] Introduce the circt-lec tool #3991

Merged
merged 39 commits into from
May 1, 2023

Conversation

frog-in-the-well
Copy link
Contributor

A logical equivalence checking tool for CIRCT.
As of now, it covers some basic hw operations and the whole comb dialect, making it suitable for combinational equivalence checking with the caveat of lowering data types like hw.array to bitvectors.
Z3 acts as the logical backend and has been introduced as a build dependency for the tool; that said I think it would merit getting added to CI before merging this PR.
Furthermore, regression tests for part of the implemented operations and features have been written. I tried to adhere to the operational semantics as defined in the CIRCT documentation, but errors or omissions might still be present. Specifically, the lack of multi-valued logic support arose during a developer's meeting.
Information about the tool's usage can be found in the README file from the tool's directory.

This has been my Google's Summer of Code project (url).
I want to thank @fabianschuiki and @maerhart for their guidance.

@uenoku
Copy link
Member

uenoku commented Sep 25, 2022

Super cool! Thank you for working this! I tried using circt-lec but I encountered a following build error (on z3-4.11.2). Do you have any idea on this?

FAILED: bin/circt-lec 
: && /usr/bin/g++ -Wuninitialized -fPIC -fno-semantic-interposition -fvisibility-inlines-hidden -Werror=date-time -Wall -Wextra -Wno-unused-parameter -Wwrite-strings -Wcast-qual -Wno-missing-field-initializers -pedantic -Wno-long-long -Wimplicit-fallthrough -Wno-maybe-uninitialized -Wno-class-memaccess -Wno-redundant-move -Wno-pessimizing-move -Wno-noexcept-type -Wdelete-non-virtual-dtor -Wsuggest-override -Wno-comment -Wno-misleading-indentation -Wctad-maybe-unsupported -fdiagnostics-color -ffunction-sections -fdata-sections -O2 -g -DNDEBUG -fuse-ld=lld -Wl,--color-diagnostics    -Wl,--gc-sections tools/circt/tools/circt-lec/CMakeFiles/circt-lec.dir/circt-lec.cpp.o tools/circt/tools/circt-lec/CMakeFiles/circt-lec.dir/Circuit.cpp.o tools/circt/tools/circt-lec/CMakeFiles/circt-lec.dir/LogicExporter.cpp.o tools/circt/tools/circt-lec/CMakeFiles/circt-lec.dir/Solver.cpp.o -o bin/circt-lec  -Wl,-rpath,:::::::::::::::::::  lib/libCIRCTCalyx.a  lib/libCIRCTESI.a  lib/libCIRCTFIRRTL.a  lib/libCIRCTFSM.a  lib/libCIRCTHandshake.a  lib/libCIRCTHWArith.a  lib/libCIRCTLLHD.a  lib/libCIRCTMoore.a  lib/libCIRCTPipelineOps.a  lib/libCIRCTSSP.a  lib/libCIRCTSystemC.a  lib/libMLIRMemRefDialect.a  lib/libMLIRArithmeticUtils.a  lib/libMLIRDialectUtils.a  lib/libCIRCTESI.a  lib/libCIRCTMSFTTransforms.a  lib/libCIRCTMSFT.a  lib/libCIRCTSeq.a  lib/libCIRCTSV.a  lib/libCIRCTComb.a  lib/libMLIRTransforms.a  lib/libMLIRCopyOpInterface.a  lib/libMLIRTransformUtils.a  lib/libMLIRRewrite.a  lib/libMLIRPDLToPDLInterp.a  lib/libMLIRPass.a  lib/libMLIRAnalysis.a  lib/libMLIRViewLikeInterface.a  lib/libMLIRLoopLikeInterface.a  lib/libMLIRDataLayoutInterfaces.a  lib/libMLIRPDLInterpDialect.a  lib/libMLIRPDLDialect.a  lib/libMLIRTranslateLib.a  lib/libMLIRParser.a  lib/libMLIRBytecodeReader.a  lib/libMLIRAsmParser.a  lib/libCIRCTSupport.a  lib/libLLVMCore.a  lib/libLLVMBinaryFormat.a  lib/libLLVMRemarks.a  lib/libLLVMBitstreamReader.a  lib/libCIRCTScheduling.a  lib/libMLIRFuncDialect.a  lib/libMLIRControlFlowDialect.a  lib/libMLIRArithmeticDialect.a  lib/libMLIRDialect.a  lib/libMLIRInferIntRangeInterface.a  lib/libMLIRControlFlowInterfaces.a  lib/libMLIRCallInterfaces.a  lib/libCIRCTHW.a  lib/libMLIRInferTypeOpInterface.a  lib/libMLIREmitCDialect.a  lib/libMLIRSideEffectInterfaces.a  lib/libMLIRCastInterfaces.a  lib/libMLIRIR.a  lib/libMLIRSupport.a  lib/libLLVMSupport.a  -lrt  -ldl  -lm  /usr/lib/x86_64-linux-gnu/libz.so  /usr/lib/x86_64-linux-gnu/libtinfo.so  lib/libLLVMDemangle.a && :
ld.lld: error: undefined symbol: Z3_mk_config
>>> referenced by z3++.h:114 (/usr/include/z3++.h:114)
>>>               tools/circt/tools/circt-lec/CMakeFiles/circt-lec.dir/circt-lec.cpp.o:(Solver::Solver())

ld.lld: error: undefined symbol: Z3_mk_context_rc
>>> referenced by z3++.h:165 (/usr/include/z3++.h:165)
>>>               tools/circt/tools/circt-lec/CMakeFiles/circt-lec.dir/circt-lec.cpp.o:(Solver::Solver())

ld.lld: error: undefined symbol: Z3_set_error_handler
>>> referenced by z3++.h:171 (/usr/include/z3++.h:171)
>>>               tools/circt/tools/circt-lec/CMakeFiles/circt-lec.dir/circt-lec.cpp.o:(Solver::Solver())

@frog-in-the-well
Copy link
Contributor Author

Hello @uenoku,
I personally build Z3 from source as a dynamic library then link it to CIRCT by specifying the build path with the -DZ3_DIR flag; I tried updating to z3-4.11.2 and it works so it's probably not an issue of Z3.

Those errors seem to indicate you're missing the Z3 C API includes, which the Z3 C++ API depend on: indeed z3++.h does #include<z3.h> and it's that which should give you access to the missing symbols (functions starting with Z3_ are from the C API).

@dtzSiFive
Copy link
Contributor

This is great! Were there any lessons/issues from trying to formally encode our semantics have been/should be propagated up as issues? That process has a tendency to turn up all sorts of overlooked details in pretty much everything that isn't already built around formal semantics (LLVM IR, ISA behavior, etc.).

Anyway having semantics defined enough to pass to a solver has a lot of exciting applications! I've often thought it'd be great to do something like Alive(2) to check our canonicalizers/folders, especially as support for multi-valued logic grows, and also to identify/verify new optimization opportunities.

Having our own LEC is awesome!


re:Z3 and building, my experience:

My distribution doesn't include the cmake bits for find_package(Z3 CONFIG) to work, but find_package(Z3) does (and builds and tests run at least, Z3 4.8.15). FWIW LLVM provides its own FindZ3.cmake, which seems to be why this works -- if I remove FindZ3.cmake from both LLVM's source and build / install tree it complains.

This seems like it might be the way to go, especially if we're okay using Z3 in same way/versions as LLVM does (requires > 4.7.1). WDYT?

@uenoku if you modify things to use find_package(Z3), does that change anything for you?

@uenoku
Copy link
Member

uenoku commented Sep 26, 2022

@uenoku if you modify things to use find_package(Z3), does that change anything for you?

That worked, thanks!

@frog-in-the-well
Copy link
Contributor Author

Were there any lessons/issues from trying to formally encode our semantics have been/should be propagated up as issues?

They're not really errors but comb.shl, comb.shrs and comb.shru having the SameTypeOperands trait is cumbersome as the number of shifts only grows logarithmically to the width of the value to be shifted.


This seems like it might be the way to go, especially if we're okay using Z3 in same way/versions as LLVM does (requires > 4.7.1). WDYT?

I've updated the procedure for locating Z3 to enter config mode if passed a custom install directory to link to and, otherwise, to search for the library in module mode. I think it should account for all use cases now.

@mikeurbach
Copy link
Contributor

This is great to see! I haven't reviewed in detail yet, but I will try to do so.

Taking the optional dependence on Z3 sounds good to me. Eventually, I think it might be nice to try to install smt-switch, so we could use one API for multiple solvers (Z3, cvc5, etc.). I think moving forward with Z3 to start is a good approach, and if we figure out how to install smt-switch, we can refactor this (and other tools using SMT solvers) to use smt-switch later.

CMakeLists.txt Outdated Show resolved Hide resolved
tools/circt-lec/README.md Outdated Show resolved Hide resolved
tools/circt-lec/Solver.h Outdated Show resolved Hide resolved
tools/circt-lec/Circuit.h Outdated Show resolved Hide resolved
tools/circt-lec/LogicExporter.cpp Outdated Show resolved Hide resolved
@TaoBi22
Copy link
Contributor

TaoBi22 commented Oct 28, 2022

This work looks super interesting! Just as an FYI, it looks like changes to CIRCT since this PR was originally created mean that this won't build when merged with the current state of main. Adding CIRCTInteropDialect to the target_link_libraries call in the circt-lec CMakeLists.txt file fixed it for me. Looking forward to seeing this merged!

@frog-in-the-well
Copy link
Contributor Author

@TaoBi22 thank you for the notice; I was indeed expecting to have to fix linking when rebasing to main.

@frog-in-the-well
Copy link
Contributor Author

With the latest commits, CI should now avoid building circt-lec with a dated Z3 version which would cause it to fail.
Once circt/images#25 gets merged and the images tags bumped, triggered workflows should then resume being representative for the tool.

teqdruid pushed a commit to circt/images that referenced this pull request Dec 13, 2022
Z3 is a state-of-the-art SMT solver widely employed in the development of verification tools.
As of now, when an image would install `clang-tidy` or `llvm`, `z3-4.8.7` would be downloaded as well as a dependency. Because this is fairly old version, CI would report errors when building llvm/circt#3991 which I've been trying to merge.
I have therefore introduced a script download and install the current latest version of Z3 available. 
Because the repo's `README` suggests to test the images locally, I also added logic to detect the host machine's architecture and download or compile an appropriate release of CMake and Z3; previously `circt-integration-test` would fail to build on my ARM machine.
Copy link
Contributor

@teqdruid teqdruid left a comment

Choose a reason for hiding this comment

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

cmake changes look good to me.

Knowing nothing else about this PR: there's an awful lot of code in the circt-lec tool. Would it make sense to put it in the circt library? tools are generally just drivers to exercise the library.

@TaoBi22
Copy link
Contributor

TaoBi22 commented Dec 13, 2022

Knowing nothing else about this PR: there's an awful lot of code in the circt-lec tool. Would it make sense to put it in the circt library? tools are generally just drivers to exercise the library.

I'm actually working on a CIRCT model checking tool at the moment that builds pretty heavily on this work, since there's a lot of overlap in the logic that it doesn't really make sense to duplicate. I was anticipating potentially having to submit a PR to move a lot of this code into the library anyway, so from that angle, it also makes sense to make that move.

Being able to put the model checking and LEC code together in the same library would also make it much easier to get equivalence checking on sequential designs up and running at some point in the future.

@frog-in-the-well
Copy link
Contributor Author

@TaoBi22 Yeah in that case sharing the code in the library makes the most sense. I'd say a new PR would be more orderly but I'm open to fix it within this PR too.


@uenoku would you please review again in view of the changes that've been made?

CMakeLists.txt Outdated Show resolved Hide resolved
@TaoBi22
Copy link
Contributor

TaoBi22 commented Jan 13, 2023

Thanks for the z3 fix @frog-in-the-well! On a branch of my fork, I've moved the bulk of the circt-lec code into the CIRCT library as discussed above - if you're happy for me to then I'll submit a stacked PR on this one containing those changes.

@frog-in-the-well
Copy link
Contributor Author

@TaoBi22 Great work, please do.

Copy link
Contributor

@TaoBi22 TaoBi22 left a comment

Choose a reason for hiding this comment

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

@frog-in-the-well seems like some erroneous semicolons will cause CI failure once approved, as can be seen in #4544's CI (though there's another issue there too - the first batch of errors relates to this problem). However, clang-format then expects indentation after each of the macro instances - maybe it's worth not linting those lines?

@frog-in-the-well
Copy link
Contributor Author

@TaoBi22

However, clang-format then expects indentation after each of the macro instances - maybe it's worth not linting those lines?

Indeed clang-format wants to improperly indent the code when removing the semicolons; in fact, even by disabling linting it would still cause issues affecting the surrounding code, for example:

-visitBinaryCombOp(ModS, comb.mods, circt::comb::ModSOp &);
+// clang-format off
+visitBinaryCombOp(ModS, comb.mods, circt::comb::ModSOp &)
 
-visitBinaryCombOp(ModU, comb.modu, circt::comb::ModUOp &);
+visitBinaryCombOp(ModU, comb.modu, circt::comb::ModUOp &)
 
-visitVariadicCombOp(Mul, comb.mul, circt::comb::MulOp &);
+visitVariadicCombOp(Mul, comb.mul, circt::comb::MulOp &)
+    // clang-format on
 
-mlir::LogicalResult
-LogicExporter::Visitor::visitComb(circt::comb::MuxOp &op,
-                                  Solver::Circuit *circuit) {
+    mlir::LogicalResult LogicExporter::Visitor::visitComb(
+        circt::comb::MuxOp &op, Solver::Circuit *circuit) {

Looking at the failed CI on the shared library PR, it looks like the semi-colons at the end of these lines cause issues when compiled using the setup used for the short integration tests (as they're unnecessary and, with those flags, forbidden, after a function definition). Getting rid of them seems to clear it up.

The reported warnings alone shouldn't cause the CI build to fail, but removing them would compromise on legibility. In case warnings have to be avoided, I could try to hack around the linter's limitations.

@TaoBi22
Copy link
Contributor

TaoBi22 commented Jan 16, 2023

@frog-in-the-well sorry, entirely my mistake - somehow misread and thought those were errors! Thanks for catching that, sorry for the false alarm!

Copy link
Member

@uenoku uenoku left a comment

Choose a reason for hiding this comment

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

Sorry for delayed review.

tools/circt-lec/LogicExporter.cpp Show resolved Hide resolved
tools/circt-lec/LogicExporter.cpp Outdated Show resolved Hide resolved
tools/circt-lec/LogicExporter.h Outdated Show resolved Hide resolved
tools/circt-lec/Solver.cpp Outdated Show resolved Hide resolved
tools/circt-lec/LogicExporter.cpp Outdated Show resolved Hide resolved
tools/circt-lec/LogicExporter.cpp Outdated Show resolved Hide resolved
tools/circt-lec/LogicExporter.cpp Outdated Show resolved Hide resolved
tools/circt-lec/CMakeLists.txt Outdated Show resolved Hide resolved
Added logic to conditionally build the tool only when detecting a Z3
installation newer than version 4.8.11. Previous versions do not provide
the `sge` and `sgt` operators in the C++ API, which are instead referenced
by the cirt-lec codebase and would cause linting and compiling issues.
Discussed with @maerhart and @teqdruid.
Adding only the supported dialects to the registry reduces the dependencies
the tool has to be linked with.
Suggested by @uenoku and @darthscsi.
Building with the install runpath could cause dependent shared libraries
to not be found.
Replace `LogicExporter::fetchModuleOp` with a
`HWInstanceLike::getReferencedModule` call.
Avoid their static construction, as suggested by @darthscsi.
As suggested by @darthscsi and discussed with @maerhart, representing
`LogicExporter` as a pass was more forced than useful.
Inline `performVariadicCombOp` and `performBinaryCombOp` macros
as suggested by @darthscsi.
For soundness reasons, `comb` operations are no longer assumed to only have
binary operators.
Suggested by @darthscsi.
- Specify `DEBUG_TYPE`s;
- handle `Solver` references instead of nullable pointers;
- remove superfluous inline specifier;
- use auto for excessive temporary values.
Suggested by @darthscsi.
- Change `circuits` field to a C-style array;
- drop `firstCircuit` parameter from `addCircuit` method;
- set statisticsOpt in constructor initialization list;
- rephrase destructor as default;
- remove empty if block;
- `printModel` and `printStatistic` on `outs` stream.
Suggested by @uenoku.

Co-authored-by: Hideto Ueno <uenoku.tokotoko@gmail.com>
Copy link
Contributor

@fabianschuiki fabianschuiki left a comment

Choose a reason for hiding this comment

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

To me this looks good to land with all the CI passing. I expect that there are a lot of small things that we'll want to tweak and refactor afterwards, for example @TaoBi22's work on bounded model checks that would love to reuse some of this code as a library.

But just adding this to CIRCT and having it open to be tweaked and worked on by everyone is a fantastic step forward! Love it! 🎉

Comment on lines +79 to +92
// comb.mul
// RUN: circt-lec %s -c1=mulBy2 -c2=addTwice -v=false | FileCheck %s --check-prefix=COMB_MUL
// COMB_MUL: c1 == c2

hw.module @mulBy2(%in: i2) -> (out: i2) {
%two = hw.constant 2 : i2
%res = comb.mul bin %in, %two : i2
hw.output %res : i2
}

hw.module @addTwice(%in: i2) -> (out: i2) {
%res = comb.add bin %in, %in : i2
hw.output %res : i2
}
Copy link
Contributor

Choose a reason for hiding this comment

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

It's very cool that we can have these kinds of self-contained tests where we just logically compare two things to each other. Very exciting. I could totally see us use this pattern to verify canonicalizations and folders which aren't supposed to functionally change the circuit 🤩

Comment on lines +26 to +48
// Defining persistent output streams such that text will be printed in
// accordance with the globally set indentation level.
inline mlir::raw_indented_ostream &dbgs() {
static auto stream = mlir::raw_indented_ostream(llvm::dbgs());
return stream;
};

inline mlir::raw_indented_ostream &errs() {
static auto stream = mlir::raw_indented_ostream(llvm::errs());
return stream;
};

inline mlir::raw_indented_ostream &outs() {
static auto stream = mlir::raw_indented_ostream(llvm::outs());
return stream;
};

/// RAII struct to indent the output streams.
struct Scope {
mlir::raw_indented_ostream::DelimitedScope indentDbgs = lec::dbgs().scope();
mlir::raw_indented_ostream::DelimitedScope indentErrs = lec::errs().scope();
mlir::raw_indented_ostream::DelimitedScope indentOuts = lec::outs().scope();
};
Copy link
Contributor

Choose a reason for hiding this comment

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

I'm curious if this would be something that's worth upstreaming to MLIR at a later point. Seems useful in general.

Comment on lines +33 to +44
class LogicExporter {
public:
LogicExporter(llvm::StringRef moduleName, Solver::Circuit *circuit)
: moduleName(moduleName), circuit(circuit){};

/// Initializes the exporting by visiting the builtin module.
mlir::LogicalResult run(mlir::ModuleOp &module);

private:
/// This class provides logic-exporting functions for the implemented
/// operations, along with a dispatcher to visit the correct handler.
struct Visitor
Copy link
Contributor

Choose a reason for hiding this comment

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

I like that this exporter is reusable. Depending on the sat problem we're trying to solve, I could see different tools using this LogicExporter to get the main Z3 mapping out of the way, and then have special handling for things like registers (e.g. to handle differences between bounded model checks and inductive proofs).

We might be able to save on some repetition by moving Visitor into the LogicExporter.cpp file and pulling all function bodies into the struct Visitor declaration. Just some nitpicking though, no need to change this PR.

Comment on lines +179 to +191
#define visitVariadicCombOp(OP_NAME, MLIR_NAME, TYPE) \
mlir::LogicalResult LogicExporter::Visitor::visitComb( \
TYPE op, Solver::Circuit *circuit) { \
LLVM_DEBUG(lec::dbgs() << "Visiting " #MLIR_NAME "\n"); \
lec::Scope indent; \
LLVM_DEBUG(debugOperands(op)); \
bool twoState = op.getTwoState(); \
REJECT_N_STATE_LOGIC(); \
mlir::Value result = op.getResult(); \
LLVM_DEBUG(debugOpResult(result)); \
circuit->perform##OP_NAME(result, op.getOperands()); \
return mlir::success(); \
}
Copy link
Contributor

Choose a reason for hiding this comment

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

One way to avoid the macro could be to have just a circuit-perform function that is overloaded for the different ops, and use the type system to switch between them. That still leaves the problem that the visitComb function bodies share a lot of code. You could have a templated function like template <class ConcreteOp> LogicalResult visitVariadicCombOp(ConcreteOp op) that you call from visitComb, and which then contains the common code.

But I think this is definitely something we can tweak and iterate on after this PR has landed, since having all of this available in the codebase is already extremely useful. And people will want to iterate and tweak and improve and extend things anyway 👍

Comment on lines +38 to +40
class Circuit;
/// Create a new circuit to be compared and return it.
Circuit *addCircuit(llvm::StringRef name);
Copy link
Contributor

Choose a reason for hiding this comment

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

After this PR lands, we could look into turning this into something like getCircuitA and getCircuitB, since the solver has this Circuit *circuit[2] member that really forces two circuits to be present. Having addCircuit makes it easy to abuse the solver by not having exactly two calls to addCircuit. Just some random thoughts on future refactorings.

Comment on lines +136 to +141
// Parse the command-line options provided by the user.
cl::ParseCommandLineOptions(
argc, argv,
"circt-lec - logical equivalence checker\n\n"
"\tThis tool compares two input circuit descriptions to determine whether"
" they are logically equivalent.\n");
Copy link
Contributor

Choose a reason for hiding this comment

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

Very cool. I love that CIRCT has gotten to a point where we can have a tool print this help output 😎

Copy link
Member

@uenoku uenoku left a comment

Choose a reason for hiding this comment

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

LGTM! Thank you for the hard work! I think there are several stuffs we can clean up, but we can improve incrementally after merging :)

@darthscsi
Copy link
Contributor

Status? What is the intention on landing?

@fabianschuiki
Copy link
Contributor

I think we should just land this as-is so others can take advantage of the Z3 dep and the logic exporter. @frog-in-the-well is that okay with you?

@frog-in-the-well
Copy link
Contributor Author

Thank you @uenoku and @fabianschuiki for the approvals.
@darthscsi I would like to land it.

@fabianschuiki fabianschuiki merged commit 5e5f3a3 into llvm:main May 1, 2023
@fabianschuiki
Copy link
Contributor

Thank you very much @frog-in-the-well for all your effort and revisions to get this landed! 🥳 💯 It's fantastic to have a solid starting point for Logical Equivalence Checking in CIRCT 🎉!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

8 participants