Add unit test discovery and execution tool (#7685)

Add the `circt-test` tool. This tool is intended to be a driver for
discovering and executing hardware unit tests in an MLIR input file. In
this first draft circt-test simply parses an MLIR assembly or bytecode
file and prints out a list of `verif.formal` operations.

This is just a starting point. We'll want this tool to be able to also
generate the Verilog code for one or more of the listed unit tests, run
the tests through tools and collect results, and much more. From a user
perspective, calling something like `circt-test design.mlirbc` should
do a sane default run of all unit tests in the provided input. But the
tool should also be useful for build systems to discover tests and run
them individually.
This commit is contained in:
Fabian Schuiki 2024-10-09 10:41:37 -07:00 committed by GitHub
parent ef3303e793
commit 6b5b63c1d1
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 243 additions and 2 deletions

View File

@ -26,6 +26,7 @@ set(CIRCT_TEST_DEPENDS
circt-dis
circt-lec
circt-opt
circt-test
circt-translate
circt-reduce
handshake-runner

View File

@ -0,0 +1,43 @@
// RUN: circt-test %s | FileCheck %s
// RUN: circt-test %s --json | FileCheck --check-prefix=JSON %s
// RUN: circt-as %s -o - | circt-test | FileCheck %s
// JSON: [
// JSON-NEXT: {
// JSON-NEXT: "name": "Some.TestA"
// JSON-NEXT: "kind": "formal"
// JSON-NEXT: }
// JSON-NEXT: {
// JSON-NEXT: "name": "Some.TestB"
// JSON-NEXT: "kind": "formal"
// JSON-NEXT: }
// CHECK: Some.TestA formal {}
// CHECK: Some.TestB formal {}
verif.formal @Some.TestA (k=42) {}
verif.formal @Some.TestB (k=42) {}
// JSON-NEXT: {
// JSON-NEXT: "name": "Attrs"
// JSON-NEXT: "kind": "formal"
// JSON-NEXT: "attrs": {
// JSON-NEXT: "awesome": true
// JSON-NEXT: "engine": "bmc"
// JSON-NEXT: "offset": 42
// JSON-NEXT: "tags": [
// JSON-NEXT: "sby"
// JSON-NEXT: "induction"
// JSON-NEXT: ]
// JSON-NEXT: "wow": false
// JSON-NEXT: }
// JSON-NEXT: }
// CHECK: Attrs formal {awesome = true, engine = "bmc", offset = 42 : i64, tags = ["sby", "induction"], wow = false}
verif.formal @Attrs (k=42) attributes {
awesome = true,
engine = "bmc",
offset = 42 : i64,
tags = ["sby", "induction"],
wow = false
} {}
// JSON: ]

View File

@ -0,0 +1,3 @@
// RUN: circt-test --help | FileCheck %s
// CHECK: OVERVIEW: Hardware unit testing tool

View File

@ -60,8 +60,8 @@ tool_dirs = [
tools = [
'arcilator', 'circt-as', 'circt-capi-ir-test', 'circt-capi-om-test',
'circt-capi-firrtl-test', 'circt-capi-firtool-test', 'circt-dis',
'circt-lec', 'circt-reduce', 'circt-translate', 'firtool', 'hlstool',
'om-linker', 'ibistool'
'circt-lec', 'circt-reduce', 'circt-test', 'circt-translate', 'firtool',
'hlstool', 'om-linker', 'ibistool'
]
if "CIRCT_OPT_CHECK_IR_ROUNDTRIP" in os.environ:

View File

@ -8,6 +8,7 @@ add_subdirectory(circt-lsp-server)
add_subdirectory(circt-opt)
add_subdirectory(circt-reduce)
add_subdirectory(circt-rtl-sim)
add_subdirectory(circt-test)
add_subdirectory(circt-translate)
add_subdirectory(firtool)
add_subdirectory(handshake-runner)

View File

@ -0,0 +1,26 @@
set(libs
CIRCTComb
CIRCTHW
CIRCTOM
CIRCTSeq
CIRCTSim
CIRCTSV
CIRCTVerif
MLIRLLVMDialect
MLIRArithDialect
MLIRControlFlowDialect
MLIRFuncDialect
MLIRSCFDialect
MLIRBytecodeReader
MLIRIR
MLIRParser
MLIRSupport
)
add_circt_tool(circt-test circt-test.cpp DEPENDS ${libs})
target_link_libraries(circt-test PRIVATE ${libs})
llvm_update_compile_flags(circt-test)
mlir_check_all_link_libraries(circt-test)

View File

@ -0,0 +1,167 @@
//===- circt-test.cpp - Hardware unit test discovery and execution tool ---===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
//
// Discover runnable unit tests in an MLIR blob and execute them through various
// backends.
//
//===----------------------------------------------------------------------===//
#include "circt/Dialect/Comb/CombDialect.h"
#include "circt/Dialect/HW/HWDialect.h"
#include "circt/Dialect/OM/OMDialect.h"
#include "circt/Dialect/SV/SVDialect.h"
#include "circt/Dialect/Seq/SeqDialect.h"
#include "circt/Dialect/Sim/SimDialect.h"
#include "circt/Dialect/Verif/VerifDialect.h"
#include "circt/Dialect/Verif/VerifOps.h"
#include "circt/Support/JSON.h"
#include "circt/Support/Version.h"
#include "mlir/Dialect/Arith/IR/Arith.h"
#include "mlir/Dialect/ControlFlow/IR/ControlFlowOps.h"
#include "mlir/Dialect/Func/IR/FuncOps.h"
#include "mlir/Dialect/LLVMIR/LLVMDialect.h"
#include "mlir/Dialect/SCF/IR/SCF.h"
#include "mlir/Parser/Parser.h"
#include "mlir/Support/FileUtilities.h"
#include "llvm/ADT/ScopeExit.h"
#include "llvm/Support/CommandLine.h"
#include "llvm/Support/InitLLVM.h"
#include "llvm/Support/SourceMgr.h"
#include "llvm/Support/ToolOutputFile.h"
#include "llvm/Support/WithColor.h"
#include <string>
using namespace llvm;
using namespace mlir;
using namespace circt;
//===----------------------------------------------------------------------===//
// Command Line Options
//===----------------------------------------------------------------------===//
namespace {
/// The tool's command line options.
struct Options {
cl::OptionCategory cat{"circt-test Options"};
cl::opt<std::string> inputFilename{cl::Positional, cl::desc("<input file>"),
cl::init("-"), cl::cat(cat)};
cl::opt<std::string> outputFilename{
"o", cl::desc("Output filename (`-` for stdout)"),
cl::value_desc("filename"), cl::init("-"), cl::cat(cat)};
cl::opt<bool> json{"json", cl::desc("Emit test list as JSON array"),
cl::init(false), cl::cat(cat)};
};
Options opts;
} // namespace
//===----------------------------------------------------------------------===//
// Tool Implementation
//===----------------------------------------------------------------------===//
/// List all the tests in a given module.
static LogicalResult listTests(ModuleOp module, llvm::raw_ostream &output) {
// Handle JSON output.
if (opts.json) {
json::OStream json(output, 2);
json.arrayBegin();
auto result = module.walk([&](Operation *op) {
if (auto formalOp = dyn_cast<verif::FormalOp>(op)) {
json.objectBegin();
auto guard = make_scope_exit([&] { json.objectEnd(); });
json.attribute("name", formalOp.getSymName());
json.attribute("kind", "formal");
auto attrs = formalOp->getDiscardableAttrDictionary();
if (!attrs.empty()) {
json.attributeBegin("attrs");
auto guard = make_scope_exit([&] { json.attributeEnd(); });
if (failed(convertAttributeToJSON(json, attrs))) {
op->emitError() << "unsupported attributes: `" << attrs
<< "` cannot be converted to JSON";
return WalkResult::interrupt();
}
}
}
return WalkResult::advance();
});
json.arrayEnd();
return failure(result.wasInterrupted());
}
// Handle regular text output.
module.walk([&](Operation *op) {
if (auto formalOp = dyn_cast<verif::FormalOp>(op)) {
output << formalOp.getSymName() << " formal"
<< " " << formalOp->getDiscardableAttrDictionary() << "\n";
}
});
return success();
}
/// Entry point for the circt-test tool. At this point an MLIRContext is
/// available, all dialects have been registered, and all command line options
/// have been parsed.
static LogicalResult execute(MLIRContext *context) {
SourceMgr srcMgr;
SourceMgrDiagnosticHandler handler(srcMgr, context);
// Open the output file for writing.
std::string errorMessage;
auto output = openOutputFile(opts.outputFilename, &errorMessage);
if (!output)
return emitError(UnknownLoc::get(context)) << errorMessage;
// Parse the input file.
auto module = parseSourceFile<ModuleOp>(opts.inputFilename, srcMgr, context);
if (!module)
return failure();
// List all tests in the input.
if (failed(listTests(*module, output->os())))
return failure();
output->keep();
return success();
}
int main(int argc, char **argv) {
InitLLVM y(argc, argv);
// Set the bug report message to indicate users should file issues on
// llvm/circt and not llvm/llvm-project.
setBugReportMsg(circtBugReportMsg);
// Print the CIRCT version when requested.
cl::AddExtraVersionPrinter(
[](raw_ostream &os) { os << getCirctVersion() << '\n'; });
// Register the dialects.
DialectRegistry registry;
registry.insert<circt::comb::CombDialect>();
registry.insert<circt::hw::HWDialect>();
registry.insert<circt::om::OMDialect>();
registry.insert<circt::seq::SeqDialect>();
registry.insert<circt::sim::SimDialect>();
registry.insert<circt::sv::SVDialect>();
registry.insert<circt::verif::VerifDialect>();
registry.insert<mlir::LLVM::LLVMDialect>();
registry.insert<mlir::func::FuncDialect>();
registry.insert<mlir::arith::ArithDialect>();
registry.insert<mlir::cf::ControlFlowDialect>();
registry.insert<mlir::scf::SCFDialect>();
// Hide default LLVM options, other than for this tool.
// MLIR options are added below.
cl::HideUnrelatedOptions({&opts.cat, &llvm::getColorCategory()});
cl::ParseCommandLineOptions(argc, argv, "Hardware unit testing tool\n");
MLIRContext context(registry);
exit(failed(execute(&context)));
}