mirror of https://github.com/llvm/circt.git
[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:
parent
02d124584f
commit
316421a20b
|
@ -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.
|
||||
|
||||
|
|
|
@ -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 |
|
|
@ -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.
|
||||
|
||||
|
|
|
@ -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)";
|
||||
}
|
||||
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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">,
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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 = [{
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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())
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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)) {
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -21,6 +21,7 @@ add_circt_dialect_library(CIRCTFIRRTLTransforms
|
|||
InnerSymbolDCE.cpp
|
||||
LowerAnnotations.cpp
|
||||
LowerCHIRRTL.cpp
|
||||
LowerIntrinsics.cpp
|
||||
LowerMemory.cpp
|
||||
LowerTypes.cpp
|
||||
LowerXMR.cpp
|
||||
|
|
|
@ -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},
|
||||
|
|
|
@ -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>();
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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>) {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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>
|
||||
}
|
||||
}
|
|
@ -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!"
|
||||
|
|
|
@ -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));
|
||||
|
|
Loading…
Reference in New Issue