[FIRRTL] Add support for "intrinsics" (#4429)

Add support for intrinsics in FIRRTL. Until intrinsics are supported in the firrtl spec, or we have more complex intrinsics, do direct lowering of intrinsics into their ops.

It is expected that eventually something like an "intmodule" will exist and annotated extern modules will not be used for intrinsics or first they will lower to intmodules internally, then intmodules will be handled uniformly.

As an example, add a sizeof operator which returns the number of bits in the argument type. This let's you query the result of type inference from inside the circuit.

Also adds support for isX as an intrinsic and plusarg sv functions.
This commit is contained in:
Andrew Lenharth 2022-12-10 10:34:18 -06:00 committed by GitHub
parent 02d124584f
commit 316421a20b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
23 changed files with 499 additions and 43 deletions

View File

@ -771,6 +771,19 @@ Example:
}
```
### circt.intrinsic
| Property | Type | Description |
| ---------- | ------ | ------------- |
| class | string | `circt.intrinsic` |
| target | string | Reference target |
| intrinsic | string | Name of Intrinsic |
Used to indicate an external module is really an intrinsic module. This exists
to allow a frontend to generate intrinsics without firrtl language support for
intrinsics. It is expect this will be deprecated as soon as the firrtl language
supports intrinsics. This annotation can only be local and applied to a module.
### SitestBlackBoxAnnotation
| Property | Type | Description |
@ -1474,3 +1487,4 @@ modules' bind file. This attribute has type `OutputFileAttr`.
Used by SVExtractTestCode. Indicates a module whose instances should be
extracted from the circuit in the indicated extraction type.

View File

@ -0,0 +1,78 @@
# Intrinsics
Intrinsics provide an implementation-specific way to extend the firrtl language
with new operations.
Intrinsics are currently implemented as annotated external modules. We expect
that native firrtl support for intrinsics will be added to the language.
## Motivation
Intrinsics provide a way to add functionality to firrtl without having to extend
the firrtl language. This allows a fast path for prototyping new operations to
rapidly repsond to output requirements. Intrinsics maintain strict definitions
and type checking.
## Supported Intrinsics
Annotations here are written in their JSON format. A "reference target"
indicates that the annotation could target any object in the hierarchy,
although there may be further restrictions in the annotation.
### circt.sizeof
Returns the size of a type. The input port is not read from and may be any
type, including uninfered types.
| Parameter | Type | Description |
| ---------- | ------ | ------------- |
| Port | Direction | Type | Description |
| ---------- | --------- | -------- | ----------------------------------- |
| i | input | Any | value whose type is to be returned |
| size | output | UInt<32> | Size of type of i |
### circt.isX
Tests if the value is a literal `x`. Firrtl doesn't have a notion of 'x per-se,
but x can come in to the system from external modules and from SV constructs.
Verification constructs need to explicitly test for 'x.
| Parameter | Type | Description |
| ---------- | ------ | ------------- |
| Port | Direction | Type | Description |
| ---------- | --------- | -------- | ----------------------------------- |
| i | input | Any | value test |
| found | output | UInt<1> | i is `x` |
### circt.plusargs.value
Tests and extracts a value from simulator command line options with system
verilog $value$plusargs. This is described in SystemVerilog 2012 section 21.6.
We do not currently check that the format string substitution flag matches the
type of the result.
| Parameter | Type | Description |
| ---------- | ------ | ------------- |
| FORMAT | string | Format string per SV 21.6 |
| Port | Direction | Type | Description |
| ---------- | --------- | -------- | ----------------------------------- |
| found | output | UInt<1> | found in args |
| result | output | AnyType | found in args |
### circt.plusargs.test
Tests simulator command line options with system verilog $test$plusargs. This
is described in SystemVerilog 2012 section 21.6.
| Parameter | Type | Description |
| ---------- | ------ | ------------- |
| FORMAT | string | Format string per SV 21.6 |
| Port | Direction | Type | Description |
| ---------- | --------- | -------- | ----------------------------------- |
| found | output | UInt<1> | found in args |

View File

@ -1064,3 +1064,17 @@ b <= mux(cond, a, inv)
It follows that interpretation (4) will then convert the false leg of the `mux`
to a constant zero.
## Intrinsics
Intrinsics are implementation-defined constructs. Intrinsics provide a way to
extend the system with funcitonality without changing the langauge. They form
an implementation-specific built-in library. Unlike traditional libraries,
implementations of intrinsics have access to internals of the compiler, allowing
them to implement features not possible in the language.
In FIRRTL, we support intrinsic modules. The internal op is `firrtl.intmodule`
which has all the properties of an external module. Until the firrtl spec
supports intrinsics, intrinsic modules are expressed in firrtl as external
modules with the `circt.intrinsic` annotation on the module.

View File

@ -473,6 +473,8 @@ class UnaryPrimOp<string mnemonic, Type srcType, Type resultType,
let parseValidator = "impl::validateUnaryOpArguments";
}
def SizeOfIntrinsicOp : UnaryPrimOp<"int.sizeof", FIRRTLBaseType, UInt32Type>;
def AsSIntPrimOp : UnaryPrimOp<"asSInt", FIRRTLBaseType, SIntType>;
def AsUIntPrimOp : UnaryPrimOp<"asUInt", FIRRTLBaseType, UIntType>;
def AsAsyncResetPrimOp
@ -613,10 +615,10 @@ def TailPrimOp : PrimOp<"tail"> {
// Verif and SV specific
//===----------------------------------------------------------------------===//
def IsXVerifOp : FIRRTLExprOp<"verif_isX"> {
def IsXIntrinsicOp : FIRRTLExprOp<"int.isX"> {
let summary = "Test for 'x";
let description = [{
The verif.isX expression checks that the operand is not a verilog literal
The int.isX expression checks that the operand is not a verilog literal
'x. Firrtl doesn't have a notion of 'x per-se, but x can come in to the
system from external modules and from SV constructs. Verification
constructs need to explicitly test for 'x.
@ -624,9 +626,25 @@ def IsXVerifOp : FIRRTLExprOp<"verif_isX"> {
let arguments = (ins FIRRTLBaseType:$arg);
let results = (outs UInt1Type:$result);
let assemblyFormat =
"$arg attr-dict `:` qualified(type($arg))";
let hasFolder = 1;
let assemblyFormat = "$arg attr-dict `:` type($arg)";
}
def PlusArgsTestIntrinsicOp : FIRRTLExprOp<"int.plusargs.test"> {
let summary = "Verilog $test$plusargs call";
let arguments = (ins StrAttr:$formatString);
let results = (outs UInt1Type:$found);
let assemblyFormat = "$formatString attr-dict";
}
def PlusArgsValueIntrinsicOp
: FIRRTLOp<"int.plusargs.value", [HasCustomSSAName, Pure]> {
let summary = "Verilog $value$plusargs call";
let arguments = (ins StrAttr:$formatString);
let results = (outs UInt1Type:$found, AnyType:$result);
let assemblyFormat = "$formatString attr-dict `:` type($result)";
}
//===----------------------------------------------------------------------===//

View File

@ -105,7 +105,7 @@ def FExtModuleOp : FIRRTLOp<"extmodule",
[IsolatedFromAbove, Symbol, HasParent<"CircuitOp">, OpAsmOpInterface,
DeclareOpInterfaceMethods<FModuleLike>, InnerSymbolTable,
DeclareOpInterfaceMethods<HWModuleLike>]> {
let summary = "FIRRTL extmodule";
let summary = "FIRRTL external module";
let description = [{
The "firrtl.extmodule" operation represents an external reference to a
Verilog module, including a given name and a list of ports.
@ -147,7 +147,7 @@ def FMemModuleOp : FIRRTLOp<"memmodule",
[IsolatedFromAbove, Symbol, HasParent<"CircuitOp">, OpAsmOpInterface,
DeclareOpInterfaceMethods<FModuleLike>, InnerSymbolTable,
DeclareOpInterfaceMethods<HWModuleLike>]> {
let summary = "FIRRTL Generated Module";
let summary = "FIRRTL Generated Memory Module";
let description = [{
The "firrtl.memmodule" operation represents an external reference to a
memory module. See the "firrtl.mem" op for a deeper explantation of the

View File

@ -102,6 +102,12 @@ def UInt1Type : FIRRTLDialectType<
"UInt<1> or UInt", "::circt::firrtl::UIntType">,
BuildableType<"UIntType::get($_builder.getContext(), 1)">;
def UInt32Type : FIRRTLDialectType<
CPred<"$_self.isa<UIntType>() && "
"$_self.cast<UIntType>().getWidth() == 32">,
"UInt<32>", "::circt::firrtl::UIntType">,
BuildableType<"UIntType::get($_builder.getContext(), 32)">;
def AsyncResetType : FIRRTLDialectType<
CPred<"$_self.isa<AsyncResetType>()">,
"AsyncReset", "::circt::firrtl::AsyncResetType">,

View File

@ -43,8 +43,8 @@ public:
// Unary operators.
AsSIntPrimOp, AsUIntPrimOp, AsAsyncResetPrimOp, AsClockPrimOp,
CvtPrimOp, NegPrimOp, NotPrimOp, AndRPrimOp, OrRPrimOp, XorRPrimOp,
// Verif Expressions.
IsXVerifOp,
// Intrinsic Expressions.
IsXIntrinsicOp, PlusArgsValueIntrinsicOp, PlusArgsTestIntrinsicOp,
// Miscellaneous.
BitsPrimOp, HeadPrimOp, MuxPrimOp, PadPrimOp, ShlPrimOp, ShrPrimOp,
TailPrimOp, VerbatimExprOp, HWStructCastOp, BitCastOp, RefSendOp,
@ -134,8 +134,10 @@ public:
HANDLE(OrRPrimOp, Unary);
HANDLE(XorRPrimOp, Unary);
// Verif Expr.
HANDLE(IsXVerifOp, Unhandled);
// Intrinsic Expr.
HANDLE(IsXIntrinsicOp, Unhandled);
HANDLE(PlusArgsValueIntrinsicOp, Unhandled);
HANDLE(PlusArgsTestIntrinsicOp, Unhandled);
// Miscellaneous.
HANDLE(BitsPrimOp, Unhandled);

View File

@ -54,6 +54,8 @@ std::unique_ptr<mlir::Pass> createLowerBundleVectorTypesPass();
std::unique_ptr<mlir::Pass> createLowerCHIRRTLPass();
std::unique_ptr<mlir::Pass> createLowerIntrinsicsPass();
std::unique_ptr<mlir::Pass> createIMConstPropPass();
std::unique_ptr<mlir::Pass>

View File

@ -609,6 +609,15 @@ def LowerXMR : Pass<"firrtl-lower-xmr", "firrtl::CircuitOp"> {
let constructor = "circt::firrtl::createLowerXMRPass()";
}
def LowerIntrinsics : Pass<"firrtl-lower-intrinsics", "firrtl::CircuitOp"> {
let summary = "Lower intrinsics";
let description = [{
This pass lowers intrinsics encoded as extmodule with annotation and
intmodule to their implementation or op.
}];
let constructor = "circt::firrtl::createLowerIntrinsicsPass()";
}
def ResolveTraces : Pass<"firrtl-resolve-traces", "firrtl::CircuitOp"> {
let summary = "Write out TraceAnnotations to an output annotation file";
let description = [{

View File

@ -1609,7 +1609,7 @@ struct FIRRTLLowering : public FIRRTLVisitor<FIRRTLLowering, LogicalResult> {
}
// Verif Operations
LogicalResult visitExpr(IsXVerifOp op);
LogicalResult visitExpr(IsXIntrinsicOp op);
// Other Operations
LogicalResult visitExpr(BitsPrimOp op);
@ -3370,7 +3370,7 @@ LogicalResult FIRRTLLowering::visitExpr(CatPrimOp op) {
// Verif Operations
//===----------------------------------------------------------------------===//
LogicalResult FIRRTLLowering::visitExpr(IsXVerifOp op) {
LogicalResult FIRRTLLowering::visitExpr(IsXIntrinsicOp op) {
auto input = getLoweredValue(op.getArg());
if (!input)
return failure();

View File

@ -888,6 +888,21 @@ LogicalResult NEQPrimOp::canonicalize(NEQPrimOp op, PatternRewriter &rewriter) {
// Unary Operators
//===----------------------------------------------------------------------===//
OpFoldResult SizeOfIntrinsicOp::fold(llvm::ArrayRef<mlir::Attribute>) {
auto base = getInput().getType().cast<FIRRTLBaseType>();
auto w = base.getBitWidthOrSentinel();
if (w >= 0)
return getIntAttr(getType(), APInt(32, w));
return {};
}
OpFoldResult IsXIntrinsicOp::fold(llvm::ArrayRef<mlir::Attribute> operands) {
// No constant can be 'x' by definition.
if (auto cst = getConstant(operands[0]))
return getIntAttr(getType(), APInt(1, 0));
return {};
}
OpFoldResult AsSIntPrimOp::fold(ArrayRef<Attribute> operands) {
// No effect.
if (getInput().getType() == getType())

View File

@ -2903,6 +2903,11 @@ LogicalResult impl::validateUnaryOpArguments(ValueRange operands,
return success();
}
FIRRTLType SizeOfIntrinsicOp::inferUnaryReturnType(FIRRTLType arg,
Optional<Location> loc) {
return UIntType::get(arg.getContext(), 32);
}
FIRRTLType AsSIntPrimOp::inferUnaryReturnType(FIRRTLType input,
Optional<Location> loc) {
auto base = input.dyn_cast<FIRRTLBaseType>();
@ -3316,12 +3321,19 @@ FIRRTLType TailPrimOp::inferReturnType(ValueRange operands,
// Verif Expressions
//===----------------------------------------------------------------------===//
FIRRTLType IsXVerifOp::inferReturnType(ValueRange operands,
ArrayRef<NamedAttribute> attrs,
Optional<Location> loc) {
FIRRTLType IsXIntrinsicOp::inferReturnType(ValueRange operands,
ArrayRef<NamedAttribute> attrs,
Optional<Location> loc) {
return UIntType::get(operands[0].getContext(), 1);
}
FIRRTLType
PlusArgsTestIntrinsicOp::inferReturnType(ValueRange operands,
ArrayRef<NamedAttribute> attrs,
Optional<Location> loc) {
return UIntType::get(attrs[0].getName().getContext(), 1);
}
//===----------------------------------------------------------------------===//
// VerbatimExprOp
//===----------------------------------------------------------------------===//
@ -3975,6 +3987,9 @@ void AndRPrimOp::getAsmResultNames(OpAsmSetValueNameFn setNameFn) {
genericAsmResultNames(*this, setNameFn);
}
void SizeOfIntrinsicOp::getAsmResultNames(OpAsmSetValueNameFn setNameFn) {
genericAsmResultNames(*this, setNameFn);
}
void AsAsyncResetPrimOp::getAsmResultNames(OpAsmSetValueNameFn setNameFn) {
genericAsmResultNames(*this, setNameFn);
}
@ -4020,13 +4035,19 @@ void GTPrimOp::getAsmResultNames(OpAsmSetValueNameFn setNameFn) {
void HeadPrimOp::getAsmResultNames(OpAsmSetValueNameFn setNameFn) {
genericAsmResultNames(*this, setNameFn);
}
void IsXVerifOp::getAsmResultNames(OpAsmSetValueNameFn setNameFn) {
void IsXIntrinsicOp::getAsmResultNames(OpAsmSetValueNameFn setNameFn) {
genericAsmResultNames(*this, setNameFn);
}
void PlusArgsValueIntrinsicOp::getAsmResultNames(
OpAsmSetValueNameFn setNameFn) {
genericAsmResultNames(*this, setNameFn);
}
void PlusArgsTestIntrinsicOp::getAsmResultNames(OpAsmSetValueNameFn setNameFn) {
genericAsmResultNames(*this, setNameFn);
}
void LEQPrimOp::getAsmResultNames(OpAsmSetValueNameFn setNameFn) {
genericAsmResultNames(*this, setNameFn);
}
void LTPrimOp::getAsmResultNames(OpAsmSetValueNameFn setNameFn) {
genericAsmResultNames(*this, setNameFn);
}

View File

@ -3012,7 +3012,7 @@ ParseResult FIRCircuitParser::parseModule(CircuitOp circuit,
// Otherwise, handle extmodule specific features like parameters.
// Parse a defname if present.
// Parse a defname if present and is an extmodule.
// TODO(firrtl spec): defname isn't documented at all, what is it?
StringRef defName;
if (consumeIf(FIRToken::kw_defname)) {

View File

@ -404,7 +404,7 @@ ParseResult circt::firrtl::foldWhenEncodedVerifOp(PrintFOp printOp) {
// Construct a `!whenCond | (value !== 1'bx)` predicate.
Value notCond = predicate;
predicate = builder.create<XorRPrimOp>(printOp.getSubstitutions()[0]);
predicate = builder.create<IsXVerifOp>(predicate);
predicate = builder.create<IsXIntrinsicOp>(predicate);
predicate = builder.create<NotPrimOp>(predicate);
predicate = builder.create<OrPrimOp>(notCond, predicate);
}

View File

@ -21,6 +21,7 @@ add_circt_dialect_library(CIRCTFIRRTLTransforms
InnerSymbolDCE.cpp
LowerAnnotations.cpp
LowerCHIRRTL.cpp
LowerIntrinsics.cpp
LowerMemory.cpp
LowerTypes.cpp
LowerXMR.cpp

View File

@ -236,7 +236,7 @@ static LogicalResult applyWithoutTargetImpl(const AnnoPathValue &target,
}
/// An applier which puts the annotation on the target and drops the 'target'
/// field from the annotaiton. Optionally handles non-local annotations.
/// field from the annotation. Optionally handles non-local annotations.
/// Ensures the target resolves to an expected type of operation.
template <bool allowNonLocal, bool allowPortAnnoTarget, typename T,
typename... Tr>
@ -294,9 +294,9 @@ struct AnnoRecord {
/// the FIRRTL Circuit, i.e., an Annotation which has no target. Historically,
/// NoTargetAnnotations were used to control the Scala FIRRTL Compiler (SFC) or
/// its passes, e.g., to set the output directory or to turn on a pass.
/// Examplesof these in the SFC are "firrtl.options.TargetDirAnnotation" to set
/// Examples of these in the SFC are "firrtl.options.TargetDirAnnotation" to set
/// the output directory or "firrtl.stage.RunFIRRTLTransformAnnotation" to
/// casuse the SFC to schedule a specified pass. Instead of leaving these
/// cause the SFC to schedule a specified pass. Instead of leaving these
/// floating or attaching them to the top-level MLIR module (which is a purer
/// interpretation of "no target"), we choose to attach them to the Circuit even
/// they do not "apply" to the Circuit. This gives later passes a common place,
@ -313,6 +313,7 @@ static const llvm::StringMap<AnnoRecord> annotationRecords{{
{"circt.testLocalOnly", {stdResolve, applyWithoutTarget<>}},
{"circt.testNT", {noResolve, applyWithoutTarget<>}},
{"circt.missing", {tryResolve, applyWithoutTarget<true>}},
{"circt.intrinsic", {stdResolve, applyWithoutTarget<false, FExtModuleOp>}},
// Grand Central Views/Interfaces Annotations
{extractGrandCentralClass, NoTargetAnnotation},
{grandCentralHierarchyFileAnnoClass, NoTargetAnnotation},

View File

@ -0,0 +1,249 @@
//===- LowerIntrinsics.cpp - Lower Intrinsics -------------------*- C++ -*-===//
//
// 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
//
//===----------------------------------------------------------------------===//
//
// This file defines the LowerIntrinsics pass. This pass processes FIRRTL
// extmodules with intrinsic annotations and rewrites the instances as
// appropriate.
//
//===----------------------------------------------------------------------===//
#include "PassDetails.h"
#include "circt/Dialect/FIRRTL/FIRRTLInstanceGraph.h"
#include "circt/Dialect/FIRRTL/FIRRTLOps.h"
#include "circt/Dialect/FIRRTL/FIRRTLTypes.h"
#include "circt/Dialect/FIRRTL/FIRRTLVisitors.h"
#include "circt/Dialect/FIRRTL/Namespace.h"
#include "circt/Dialect/FIRRTL/Passes.h"
#include "mlir/IR/Diagnostics.h"
#include "llvm/ADT/APSInt.h"
#include "llvm/ADT/PostOrderIterator.h"
#include "llvm/ADT/StringExtras.h"
#include "llvm/Support/Debug.h"
using namespace circt;
using namespace firrtl;
// Pass Infrastructure
//===----------------------------------------------------------------------===//
namespace {
struct LowerIntrinsicsPass : public LowerIntrinsicsBase<LowerIntrinsicsPass> {
void runOnOperation() override;
};
} // end anonymous namespace
static ParseResult hasNPorts(StringRef name, FExtModuleOp mod, unsigned n) {
if (mod.getPorts().size() != n) {
mod.emitError(name) << " has " << mod.getPorts().size()
<< " ports instead of " << n;
return failure();
}
return success();
}
static ParseResult namedPort(StringRef name, FExtModuleOp mod, unsigned n,
StringRef portName) {
auto ports = mod.getPorts();
if (n >= ports.size()) {
mod.emitError(name) << " missing port " << n;
return failure();
}
if (!ports[n].getName().equals(portName)) {
mod.emitError(name) << " port " << n << " named '" << ports[n].getName()
<< "' instead of '" << portName << "'";
return failure();
}
return success();
}
template <typename T>
static ParseResult sizedPort(StringRef name, FExtModuleOp mod, unsigned n,
int32_t size) {
auto ports = mod.getPorts();
if (n >= ports.size()) {
mod.emitError(name) << " missing port " << n;
return failure();
}
if (!ports[n].type.isa<T>()) {
mod.emitError(name) << " port " << n << " not a correct type";
return failure();
}
if (ports[n].type.cast<T>().getWidth() != size) {
mod.emitError(name) << " port " << n << " not size " << size;
return failure();
}
return success();
}
static ParseResult hasNParam(StringRef name, FExtModuleOp mod, unsigned n) {
unsigned num = 0;
if (mod.getParameters())
num = mod.getParameters().size();
if (n != num) {
mod.emitError(name) << " has " << num << " parameters instead of " << n;
return failure();
}
return success();
}
static ParseResult namedParam(StringRef name, FExtModuleOp mod,
StringRef paramName) {
for (auto a : mod.getParameters()) {
auto param = a.cast<ParamDeclAttr>();
if (param.getName().getValue().equals(paramName)) {
if (param.getValue().isa<StringAttr>())
return success();
mod.emitError(name) << " test has parameter '" << param.getName()
<< "' which should be a string but is not";
return failure();
}
}
mod.emitError(name) << " is missing parameter " << paramName;
return failure();
}
static bool lowerCirctSizeof(InstancePathCache &instancePathCache,
FExtModuleOp mod) {
auto ports = mod.getPorts();
if (hasNPorts("circt.sizeof", mod, 2) ||
namedPort("circt.sizeof", mod, 0, "i") ||
namedPort("circt.sizeof", mod, 1, "size") ||
sizedPort<UIntType>("circt.sizeof", mod, 1, 32) ||
hasNParam("circt.sizeof", mod, 0))
return false;
for (auto *use : instancePathCache.instanceGraph[mod]->uses()) {
auto inst = cast<InstanceOp>(use->getInstance().getOperation());
ImplicitLocOpBuilder builder(inst.getLoc(), inst);
auto inputWire = builder.create<WireOp>(ports[0].type);
inst.getResult(0).replaceAllUsesWith(inputWire);
auto size = builder.create<SizeOfIntrinsicOp>(inputWire);
inst.getResult(1).replaceAllUsesWith(size);
inst.erase();
}
return true;
}
static bool lowerCirctIsX(InstancePathCache &instancePathCache,
FExtModuleOp mod) {
auto ports = mod.getPorts();
if (hasNPorts("circt.isaX", mod, 2) || namedPort("circt.isX", mod, 0, "i") ||
namedPort("circt.isX", mod, 1, "found") ||
sizedPort<UIntType>("circt.isX", mod, 1, 1) ||
hasNParam("circt.isX", mod, 0))
return false;
for (auto *use : instancePathCache.instanceGraph[mod]->uses()) {
auto inst = cast<InstanceOp>(use->getInstance().getOperation());
ImplicitLocOpBuilder builder(inst.getLoc(), inst);
auto inputWire = builder.create<WireOp>(ports[0].type);
inst.getResult(0).replaceAllUsesWith(inputWire);
auto size = builder.create<IsXIntrinsicOp>(inputWire);
inst.getResult(1).replaceAllUsesWith(size);
inst.erase();
}
return true;
}
static bool lowerCirctPlusArgTest(InstancePathCache &instancePathCache,
FExtModuleOp mod) {
if (hasNPorts("circt.plusargs.test", mod, 1) ||
namedPort("circt.plusargs.test", mod, 0, "found") ||
sizedPort<UIntType>("circt.plusargs.test", mod, 0, 1) ||
hasNParam("circt.plusargs.test", mod, 1) ||
namedParam("circt.plusargs.test", mod, "FORMAT"))
return false;
auto param = mod.getParameters()[0].cast<ParamDeclAttr>();
for (auto *use : instancePathCache.instanceGraph[mod]->uses()) {
auto inst = cast<InstanceOp>(use->getInstance().getOperation());
ImplicitLocOpBuilder builder(inst.getLoc(), inst);
auto newop = builder.create<PlusArgsTestIntrinsicOp>(
param.getValue().cast<StringAttr>());
inst.getResult(0).replaceAllUsesWith(newop);
inst.erase();
}
return true;
}
static bool lowerCirctPlusArgValue(InstancePathCache &instancePathCache,
FExtModuleOp mod) {
if (hasNPorts("circt.plusargs.value", mod, 2) ||
namedPort("circt.plusargs.value", mod, 0, "found") ||
namedPort("circt.plusargs.value", mod, 1, "result") ||
sizedPort<UIntType>("circt.plusargs.value", mod, 0, 1) ||
hasNParam("circt.plusargs.value", mod, 1) ||
namedParam("circt.plusargs.value", mod, "FORMAT"))
return false;
auto param = mod.getParameters()[0].cast<ParamDeclAttr>();
for (auto *use : instancePathCache.instanceGraph[mod]->uses()) {
auto inst = cast<InstanceOp>(use->getInstance().getOperation());
ImplicitLocOpBuilder builder(inst.getLoc(), inst);
auto newop = builder.create<PlusArgsValueIntrinsicOp>(
inst.getResultTypes(), param.getValue().cast<StringAttr>());
inst.getResult(0).replaceAllUsesWith(newop.getFound());
inst.getResult(1).replaceAllUsesWith(newop.getResult());
inst.erase();
}
return true;
}
std::pair<const char *, std::function<bool(InstancePathCache &, FExtModuleOp)>>
intrinsics[] = {
{"circt.sizeof", lowerCirctSizeof},
{"circt.isX", lowerCirctIsX},
{"circt.plusargs.test", lowerCirctPlusArgTest},
{"circt.plusargs.value", lowerCirctPlusArgValue},
};
// This is the main entrypoint for the lowering pass.
void LowerIntrinsicsPass::runOnOperation() {
size_t numFailures = 0;
size_t numConverted = 0;
InstancePathCache instancePathCache(getAnalysis<InstanceGraph>());
for (auto op :
llvm::make_early_inc_range(getOperation().getOps<FExtModuleOp>())) {
auto anno = AnnotationSet(op).getAnnotation("circt.intrinsic");
if (!anno)
continue;
auto intname = anno.getMember<StringAttr>("intrinsic");
if (!intname) {
op.emitError("Intrinsic annotation with no intrinsic name");
++numFailures;
continue;
}
bool found = false;
for (const auto &intrinsic : intrinsics) {
if (intname.getValue().equals(intrinsic.first)) {
found = true;
if (intrinsic.second(instancePathCache, op)) {
++numConverted;
op.erase();
} else {
++numFailures;
}
break;
}
}
if (!found) {
op.emitError("Unknown intrinsic '") << intname << "'";
++numFailures;
}
}
if (numFailures)
signalPassFailure();
if (!numConverted)
markAllAnalysesPreserved();
}
/// This is the pass constructor.
std::unique_ptr<mlir::Pass> circt::firrtl::createLowerIntrinsicsPass() {
return std::make_unique<LowerIntrinsicsPass>();
}

View File

@ -208,7 +208,7 @@ firrtl.circuit "Simple" attributes {annotations = [{class =
%28 = firrtl.andr %18 : (!firrtl.uint<14>) -> !firrtl.uint<1>
// CHECK-NEXT: = comb.icmp bin ceq {{.*}}, %x_i1
%x28 = firrtl.verif_isX %28 : !firrtl.uint<1>
%x28 = firrtl.int.isX %28 : !firrtl.uint<1>
// CHECK-NEXT: [[XOREXT:%.+]] = comb.concat %c0_i11, [[XOR]]
// CHECK-NEXT: [[SHIFT:%.+]] = comb.shru bin [[XOREXT]], [[VAL18]] : i14

View File

@ -169,24 +169,6 @@ firrtl.circuit "padConstReg" {
}
}
// TODO: This requires SFCCompat
// "pad zero when constant propping a register replaced with zero"
firrtl.circuit "padZeroReg" {
// CHECK-LABEL: firrtl.module @padZeroReg
firrtl.module @padZeroReg(in %clock: !firrtl.clock, out %z: !firrtl.uint<16>) {
%_r = firrtl.reg droppable_name %clock : !firrtl.uint<8>
%c0_ui1 = firrtl.constant 0 : !firrtl.uint<1>
%0 = firrtl.or %_r, %c0_ui1 : (!firrtl.uint<8>, !firrtl.uint<1>) -> !firrtl.uint<8>
firrtl.connect %_r, %0 : !firrtl.uint<8>, !firrtl.uint<8>
%c171_ui8 = firrtl.constant 171 : !firrtl.uint<8>
%_n = firrtl.node droppable_name %c171_ui8 : !firrtl.uint<8>
%1 = firrtl.cat %_n, %_r : (!firrtl.uint<8>, !firrtl.uint<8>) -> !firrtl.uint<16>
firrtl.connect %z, %1 : !firrtl.uint<16>, !firrtl.uint<16>
// _HECK: %[[TMP:.+]] = firrtl.constant 43776 : !firrtl.uint<16>
// _HECK-NEXT: firrtl.strictconnect %z, %[[TMP]] : !firrtl.uint<16>
}
}
// should "pad constant connections to outputs when propagating"
firrtl.circuit "padConstOut" {
firrtl.module private @padConstOutChild(out %x: !firrtl.uint<8>) {

View File

@ -2488,7 +2488,7 @@ firrtl.module @AnnotationsBlockRemoval(
}
// CHECK-LABEL: firrtl.module @Verification
firrtl.module @Verification(in %clock: !firrtl.clock, in %p: !firrtl.uint<1>) {
firrtl.module @Verification(in %clock: !firrtl.clock, in %p: !firrtl.uint<1>, out %o : !firrtl.uint<1>) {
%c0 = firrtl.constant 0 : !firrtl.uint<1>
%c1 = firrtl.constant 1 : !firrtl.uint<1>
@ -2507,6 +2507,10 @@ firrtl.module @Verification(in %clock: !firrtl.clock, in %p: !firrtl.uint<1>) {
firrtl.assume %clock, %c1, %p, "assume1"
// CHECK-NOT: firrtl.cover
firrtl.cover %clock, %c0, %p, "cover0"
// CHECK-NOT: firrtl.int.isX
%x = firrtl.int.isX %c0 : !firrtl.uint<1>
firrtl.strictconnect %o, %x : !firrtl.uint<1>
}
// COMMON-LABEL: firrtl.module @MultibitMux

View File

@ -0,0 +1,38 @@
// RUN: circt-opt --pass-pipeline='builtin.module(firrtl.circuit(firrtl-lower-intrinsics))' %s | FileCheck %s
// CHECK-LABEL: "Foo"
firrtl.circuit "Foo" {
// CHECK-NOT: NameDoesNotMatter
firrtl.extmodule @NameDoesNotMatter(in i : !firrtl.clock, out size : !firrtl.uint<32>) attributes
{annotations = [{class = "circt.intrinsic", intrinsic = "circt.sizeof"}]}
// CHECK-NOT: NameDoesNotMatter2
firrtl.extmodule @NameDoesNotMatter2(in i : !firrtl.clock, out found : !firrtl.uint<1>) attributes
{annotations = [{class = "circt.intrinsic", intrinsic = "circt.isX"}]}
// CHECK-NOT: NameDoesNotMatter3
firrtl.extmodule @NameDoesNotMatter3<FORMAT: none = "foo">(out found : !firrtl.uint<1>) attributes
{annotations = [{class = "circt.intrinsic", intrinsic = "circt.plusargs.test"}]}
// CHECK-NOT: NameDoesNotMatter4
firrtl.extmodule @NameDoesNotMatter4<FORMAT: none = "foo">(out found : !firrtl.uint<1>, out result: !firrtl.uint<5>) attributes
{annotations = [{class = "circt.intrinsic", intrinsic = "circt.plusargs.value"}]}
// CHECK: Foo
firrtl.module @Foo(in %clk : !firrtl.clock) {
%i1, %size = firrtl.instance "" @NameDoesNotMatter(in i : !firrtl.clock, out size : !firrtl.uint<32>)
// CHECK-NOT: NameDoesNotMatter
// CHECK: firrtl.int.sizeof
firrtl.strictconnect %i1, %clk : !firrtl.clock
%i2, %found2 = firrtl.instance "" @NameDoesNotMatter2(in i : !firrtl.clock, out found : !firrtl.uint<1>)
// CHECK-NOT: NameDoesNotMatter2
// CHECK: firrtl.int.isX
firrtl.strictconnect %i2, %clk : !firrtl.clock
%found3 = firrtl.instance "" @NameDoesNotMatter3(out found : !firrtl.uint<1>)
// CHECK-NOT: NameDoesNotMatter3
// CHECK: firrtl.int.plusargs.test "foo"
%found4, %result1 = firrtl.instance "" @NameDoesNotMatter4(out found : !firrtl.uint<1>, out result: !firrtl.uint<5>)
// CHECK-NOT: NameDoesNotMatter4
// CHECK: %5:2 = firrtl.int.plusargs.value "foo" : !firrtl.uint<5>
}
}

View File

@ -999,7 +999,7 @@ circuit MyModule : ; CHECK: firrtl.circuit "MyModule" {
printf(clock, enable, "assertNotX:%d:value must not be X!", value)
; CHECK: [[TMP1:%.+]] = firrtl.not %cond
; CHECK: [[TMP2:%.+]] = firrtl.xorr %value
; CHECK: [[TMP3:%.+]] = firrtl.verif_isX
; CHECK: [[TMP3:%.+]] = firrtl.int.isX
; CHECK: [[TMP4:%.+]] = firrtl.not
; CHECK: [[TMP5:%.+]] = firrtl.or [[TMP1]], [[TMP4]]
; CHECK: firrtl.assert %clock, [[TMP5]], %enable, "value must not be X!"

View File

@ -612,6 +612,8 @@ processBuffer(MLIRContext &context, TimingScope &ts, llvm::SourceMgr &sourceMgr,
return success();
}
pm.nest<firrtl::CircuitOp>().addPass(firrtl::createLowerIntrinsicsPass());
// TODO: Move this to the O1 pipeline.
pm.nest<firrtl::CircuitOp>().nest<firrtl::FModuleOp>().addPass(
firrtl::createDropNamesPass(preserveMode));