[RTL] Implement some new combinatorial ops, implement VerilogEmitter

support for them, implement a new rtl::CombinatorialVistor class.
This commit is contained in:
Chris Lattner 2020-05-16 23:19:59 -07:00
parent 397c2f48c9
commit 45fc170ab1
10 changed files with 219 additions and 51 deletions

View File

@ -16,6 +16,9 @@ namespace rtl {
#define GET_OP_CLASSES
#include "cirt/Dialect/RTL/RTL.h.inc"
/// Return true if the specified operation is a combinatorial logic op.
bool isCombinatorial(Operation *op);
} // namespace rtl
} // namespace cirt

View File

@ -18,6 +18,10 @@ def ConstantOp : RTLOp<"constant", [NoSideEffect, ConstantLike,
let arguments = (ins APIntAttr:$value);
let results = (outs AnySignlessInteger:$result);
// FIXME(QoI): Instead of requiring "rtl.constant (42: i8) : i8", we should
// just use "rtl.constant 42: i8". This can be done with a custom printer and
// parser, but would be better to be autoderived from the
// FirstAttrDerivedResultType trait.
let assemblyFormat = [{
`(` $value `)` attr-dict `:` type($result)
}];
@ -54,10 +58,15 @@ class UTBinRTLOp<string mnemonic, list<OpTrait> traits = []> :
}];
}
// Arithmetic and Logical Binary Operations.
def AddOp : UTBinRTLOp<"add", [Commutative]>;
def SubOp : UTBinRTLOp<"sub">;
def MulOp : UTBinRTLOp<"mul", [Commutative]>;
def DivOp : UTBinRTLOp<"div">;
def RemOp : UTBinRTLOp<"rem">;
def AndOp : UTBinRTLOp<"and", [Commutative]>;
def OrOp : UTBinRTLOp<"or", [Commutative]>;
def XorOp : UTBinRTLOp<"xor", [Commutative]>;
//===----------------------------------------------------------------------===//
// Other Operations

View File

@ -0,0 +1,81 @@
//===- RTL/Visitors.h - RTL Dialect Visitors --------------------*- C++ -*-===//
//
// This file defines visitors that make it easier to work with RTL IR.
//
//===----------------------------------------------------------------------===//
#ifndef CIRT_DIALECT_RTL_VISITORS_H
#define CIRT_DIALECT_RTL_VISITORS_H
#include "cirt/Dialect/RTL/Ops.h"
#include "llvm/ADT/TypeSwitch.h"
namespace cirt {
namespace rtl {
/// This helps visit Combinatorial nodes.
template <typename ConcreteType, typename ResultType = void,
typename... ExtraArgs>
class CombinatorialVisitor {
public:
ResultType dispatchCombinatorialVisitor(Operation *op, ExtraArgs... args) {
auto *thisCast = static_cast<ConcreteType *>(this);
return TypeSwitch<Operation *, ResultType>(op)
.template Case<ConstantOp,
// Arithmetic and Logical Binary Operations.
AddOp, SubOp, MulOp, DivOp, RemOp, AndOp, OrOp, XorOp,
// Other operations.
ConcatOp>([&](auto expr) -> ResultType {
return thisCast->visitComb(expr, args...);
})
.Default([&](auto expr) -> ResultType {
return thisCast->visitInvalidComb(op, args...);
});
}
/// This callback is invoked on any non-expression operations.
ResultType visitInvalidComb(Operation *op, ExtraArgs... args) {
op->emitOpError("unknown RTL combinatorial node");
abort();
}
/// This callback is invoked on any combinatorial operations that are not
/// handled by the concrete visitor.
ResultType visitUnhandledComb(Operation *op, ExtraArgs... args) {
return ResultType();
}
/// This fallback is invoked on any binary node that isn't explicitly handled.
/// The default implementation delegates to the 'unhandled' fallback.
ResultType visitBinaryComb(Operation *op, ExtraArgs... args) {
return static_cast<ConcreteType *>(this)->visitUnhandledComb(op, args...);
}
#define HANDLE(OPTYPE, OPKIND) \
ResultType visitComb(OPTYPE op, ExtraArgs... args) { \
return static_cast<ConcreteType *>(this)->visit##OPKIND##Comb(op, \
args...); \
}
// Basic nodes.
HANDLE(ConstantOp, Unhandled)
// Arithmetic and Logical Binary Operations.
HANDLE(AddOp, Binary);
HANDLE(SubOp, Binary);
HANDLE(MulOp, Binary);
HANDLE(DivOp, Binary);
HANDLE(RemOp, Binary);
HANDLE(AndOp, Binary);
HANDLE(OrOp, Binary);
HANDLE(XorOp, Binary);
// Other operations.
HANDLE(ConcatOp, Unhandled);
#undef HANDLE
};
} // namespace rtl
} // namespace cirt
#endif // CIRT_DIALECT_RTL_VISITORS_H

View File

@ -3,6 +3,7 @@
//===----------------------------------------------------------------------===//
#include "cirt/Dialect/RTL/Ops.h"
#include "cirt/Dialect/RTL/Visitors.h"
#include "mlir/IR/Builders.h"
#include "mlir/IR/DialectImplementation.h"
#include "mlir/IR/StandardTypes.h"
@ -10,6 +11,21 @@
using namespace cirt;
using namespace rtl;
/// Return true if the specified operation is a combinatorial logic op.
bool rtl::isCombinatorial(Operation *op) {
struct IsCombClassifier
: public CombinatorialVisitor<IsCombClassifier, bool> {
bool visitInvalidComb(Operation *op) { return false; }
bool visitUnhandledComb(Operation *op) { return true; }
};
return IsCombClassifier().dispatchCombinatorialVisitor(op);
}
//===----------------------------------------------------------------------===//
// ConstantOp
//===----------------------------------------------------------------------===//
static LogicalResult verify(ConstantOp constant) {
// If the result type has a bitwidth, then the attribute must match its width.
auto intType = constant.getType().cast<IntegerType>();

View File

@ -7,4 +7,5 @@ add_mlir_library(CIRTEmitVerilog
LINK_LIBS PUBLIC
MLIRFIRRTL
MLIRRTL
)

View File

@ -6,8 +6,9 @@
#include "cirt/EmitVerilog.h"
#include "cirt/Dialect/FIRRTL/Visitors.h"
#include "cirt/Dialect/RTL/Ops.h"
#include "cirt/Dialect/RTL/Visitors.h"
#include "cirt/Support/LLVM.h"
#include "mlir/Dialect/StandardOps/IR/Ops.h"
#include "mlir/IR/Module.h"
#include "mlir/IR/StandardTypes.h"
#include "mlir/Translation.h"
@ -34,11 +35,9 @@ static llvm::ManagedStatic<StringSet<>> reservedWordCache;
//===----------------------------------------------------------------------===//
static bool isVerilogExpression(Operation *op) {
// All FIRRTL expressions are also Verilog expressions.
if (isExpression(op))
return true;
// The standard ConstantIntOp is an expression as well.
return isa<mlir::ConstantIntOp>(op);
// All FIRRTL expressions and RTL combinatorial logic ops are Verilog
// expressions.
return isExpression(op) || rtl::isCombinatorial(op);
}
/// Return the width of the specified FIRRTL type in bits or -1 if it isn't
@ -673,7 +672,8 @@ namespace {
/// we emit the characters to a SmallVector which allows us to emit a bunch of
/// stuff, then pre-insert parentheses and other things if we find out that it
/// was needed later.
class ExprEmitter : public ExprVisitor<ExprEmitter, SubExprInfo> {
class ExprEmitter : public ExprVisitor<ExprEmitter, SubExprInfo>,
public rtl::CombinatorialVisitor<ExprEmitter, SubExprInfo> {
public:
/// Create an ExprEmitter for the specified module emitter, and keeping track
/// of any emitted expressions in the specified set.
@ -685,6 +685,7 @@ public:
/// Emit the specified expression and return it as a string.
std::string emitExpressionToString(Value exp, VerilogPrecedence precedence);
friend class ExprVisitor;
friend class CombinatorialVisitor;
/// Do a best-effort job of looking through noop cast operations.
Value lookThroughNoopCasts(Value value) {
@ -702,11 +703,16 @@ private:
bool forceExpectedSign = false);
SubExprInfo visitUnhandledExpr(Operation *op);
SubExprInfo visitInvalidExpr(Operation *op);
SubExprInfo visitInvalidExpr(Operation *op) {
return dispatchCombinatorialVisitor(op);
}
SubExprInfo visitInvalidComb(Operation *op) { return visitUnhandledExpr(op); }
SubExprInfo visitUnhandledComb(Operation *op) {
return visitUnhandledExpr(op);
}
using ExprVisitor::visitExpr;
SubExprInfo visitExpr(firrtl::ConstantOp op);
SubExprInfo visitExpr(mlir::ConstantIntOp op);
/// Emit a verilog concatenation of the specified values. If the before or
/// after strings are specified, they are included as prefix/postfix elements
@ -719,35 +725,7 @@ private:
SubExprInfo emitBitSelect(Value operand, unsigned hiBit, unsigned loBit);
SubExprInfo emitBinary(Operation *op, VerilogPrecedence prec,
const char *syntax, bool hasStrictSign = false) {
auto lhsInfo = emitSubExpr(op->getOperand(0), prec, hasStrictSign);
os << ' ' << syntax << ' ';
// The precedence of the RHS operand must be tighter than this operator if
// they have a different opcode in order to handle things like "x-(a+b)".
// This isn't needed on the LHS, because the relevant Verilog operators are
// left-associative.
//
auto *rhsOperandOp =
lookThroughNoopCasts(op->getOperand(1)).getDefiningOp();
auto rhsPrec = VerilogPrecedence(prec - 1);
if (rhsOperandOp && op->getName() == rhsOperandOp->getName())
rhsPrec = prec;
auto rhsInfo = emitSubExpr(op->getOperand(1), rhsPrec, hasStrictSign);
// If we have a strict sign, then match the firrtl operation sign.
// Otherwise, the result is signed if both operands are signed.
SubExprSignedness signedness;
if (hasStrictSign)
signedness = getSignednessOf(op->getResult(0).getType());
else if (lhsInfo.signedness == IsSigned && rhsInfo.signedness == IsSigned)
signedness = IsSigned;
else
signedness = IsUnsigned;
return {prec, signedness};
}
const char *syntax, bool hasStrictSign = false);
/// Emit the specified subexpression in a context where the sign matters,
/// e.g. for a less than comparison or divide.
@ -841,6 +819,20 @@ private:
// Conversion to/from standard integer types is a noop.
SubExprInfo visitExpr(StdIntCast op) { return emitNoopCast(op); }
// RTL Dialect Operations
using CombinatorialVisitor::visitComb;
SubExprInfo visitComb(rtl::ConstantOp op);
SubExprInfo visitComb(rtl::AddOp op) { return emitBinary(op, Addition, "+"); }
SubExprInfo visitComb(rtl::SubOp op) { return emitBinary(op, Addition, "-"); }
SubExprInfo visitComb(rtl::MulOp op) { return emitBinary(op, Multiply, "*"); }
SubExprInfo visitComb(rtl::DivOp op) {
return emitSignedBinary(op, Multiply, "/");
}
SubExprInfo visitComb(rtl::AndOp op) { return emitBinary(op, And, "&"); }
SubExprInfo visitComb(rtl::OrOp op) { return emitBinary(op, Or, "|"); }
SubExprInfo visitComb(rtl::XorOp op) { return emitBinary(op, Xor, "^"); }
SubExprInfo visitComb(rtl::ConcatOp op);
private:
SmallPtrSet<Operation *, 8> &emittedExprs;
SmallString<128> resultBuffer;
@ -869,6 +861,36 @@ std::string ExprEmitter::emitExpressionToString(Value exp,
return std::string(resultBuffer.begin(), resultBuffer.end());
}
SubExprInfo ExprEmitter::emitBinary(Operation *op, VerilogPrecedence prec,
const char *syntax, bool hasStrictSign) {
auto lhsInfo = emitSubExpr(op->getOperand(0), prec, hasStrictSign);
os << ' ' << syntax << ' ';
// The precedence of the RHS operand must be tighter than this operator if
// they have a different opcode in order to handle things like "x-(a+b)".
// This isn't needed on the LHS, because the relevant Verilog operators are
// left-associative.
//
auto *rhsOperandOp = lookThroughNoopCasts(op->getOperand(1)).getDefiningOp();
auto rhsPrec = VerilogPrecedence(prec - 1);
if (rhsOperandOp && op->getName() == rhsOperandOp->getName())
rhsPrec = prec;
auto rhsInfo = emitSubExpr(op->getOperand(1), rhsPrec, hasStrictSign);
// If we have a strict sign, then match the firrtl operation sign.
// Otherwise, the result is signed if both operands are signed.
SubExprSignedness signedness;
if (hasStrictSign)
signedness = getSignednessOf(op->getResult(0).getType());
else if (lhsInfo.signedness == IsSigned && rhsInfo.signedness == IsSigned)
signedness = IsSigned;
else
signedness = IsUnsigned;
return {prec, signedness};
}
/// Emit the specified value as a subexpression to the stream.
SubExprInfo ExprEmitter::emitSubExpr(Value exp,
VerilogPrecedence parenthesizeIfLooserThan,
@ -967,6 +989,15 @@ SubExprInfo ExprEmitter::emitCat(ArrayRef<Value> values, StringRef before,
return {Unary, IsUnsigned};
}
SubExprInfo ExprEmitter::visitComb(rtl::ConcatOp op) {
os << '{';
llvm::interleaveComma(op.getOperands(), os,
[&](Value v) { emitSubExpr(v, LowestPrecedence); });
os << '}';
return {Unary, IsUnsigned};
}
/// Emit a verilog bit selection operation like x[4:0], the bit numbers are
/// inclusive like verilog.
///
@ -1001,7 +1032,7 @@ SubExprInfo ExprEmitter::visitExpr(firrtl::ConstantOp op) {
return {Unary, resType.isSigned() ? IsSigned : IsUnsigned};
}
SubExprInfo ExprEmitter::visitExpr(mlir::ConstantIntOp op) {
SubExprInfo ExprEmitter::visitComb(rtl::ConstantOp op) {
auto resType = op.getType().cast<IntegerType>();
os << resType.getWidth() << '\'';
if (resType.isSigned())
@ -1126,14 +1157,6 @@ SubExprInfo ExprEmitter::visitUnhandledExpr(Operation *op) {
return {Symbol, IsUnsigned};
}
// This handles dispatching to non-FIRRTL operations.
SubExprInfo ExprEmitter::visitInvalidExpr(Operation *op) {
if (auto cst = dyn_cast<mlir::ConstantIntOp>(op))
return visitExpr(cst);
return visitUnhandledExpr(op);
}
//===----------------------------------------------------------------------===//
// Statements
//===----------------------------------------------------------------------===//
@ -1164,7 +1187,7 @@ void ModuleEmitter::emitStatementExpression(Operation *op) {
if (op->getResult(0).use_empty()) {
indent() << "// Unused: ";
} else if (emitInlineWireDecls) {
auto type = op->getResult(0).getType().cast<FIRRTLType>();
auto type = op->getResult(0).getType();
indent() << "wire ";
if (getBitWidthOrSentinel(type) != 1) {
@ -1586,7 +1609,7 @@ static bool isExpressionUnableToInline(Operation *op) {
/// Return true for operations that are always inlined.
static bool isExpressionAlwaysInline(Operation *op) {
if (isa<firrtl::ConstantOp>(op) || isa<mlir::ConstantIntOp>(op) ||
if (isa<firrtl::ConstantOp>(op) || isa<rtl::ConstantOp>(op) ||
isa<SubfieldOp>(op))
return true;

View File

@ -0,0 +1,29 @@
// RUN: cirt-translate %s -emit-verilog -verify-diagnostics | FileCheck %s --strict-whitespace
firrtl.circuit "Circuit" {
firrtl.module @M1(%x : !firrtl.uint<8>,
%y : !firrtl.flip<uint<8>>,
%z : i8) {
%c42 = rtl.constant (42 : i8) : i8
%c5 = rtl.constant (5 : i8) : i8
%a = rtl.add %z, %c42 : i8
%b = rtl.mul %a, %c5 : i8
%c = firrtl.stdIntCast %b : (i8) -> !firrtl.uint<8>
firrtl.connect %y, %c : !firrtl.flip<uint<8>>, !firrtl.uint<8>
%d = rtl.mul %z, %z : i8
%e = rtl.concat %d, %z, %d : (i8, i8, i8) -> i8
%f = firrtl.stdIntCast %e : (i8) -> !firrtl.uint<8>
firrtl.connect %y, %f : !firrtl.flip<uint<8>>, !firrtl.uint<8>
}
// CHECK-LABEL: module M1(
// CHECK-NEXT: input [7:0] x,
// CHECK-NEXT: output [7:0] y,
// CHECK-NEXT: input [7:0] z);
// CHECK-EMPTY:
// CHECK-NEXT: assign y = (z + 8'h2A) * 8'h5;
// CHECK-NEXT: wire [7:0] _T = z * z;
// CHECK-NEXT: assign y = {_T, z, _T};
// CHECK-NEXT: endmodule
}

View File

@ -29,7 +29,7 @@ firrtl.circuit "Circuit" {
%c42_ui8 = firrtl.constant(42 : ui8) : !firrtl.uint<8>
firrtl.connect %y, %c42_ui8 : !firrtl.flip<uint<8>>, !firrtl.uint<8>
%c42 = constant 42 : i8
%c42 = rtl.constant (42: i8) : i8
%a = firrtl.stdIntCast %c42 : (i8) -> !firrtl.uint<8>
firrtl.connect %y, %a : !firrtl.flip<uint<8>>, !firrtl.uint<8>

View File

@ -6,6 +6,7 @@
//===----------------------------------------------------------------------===//
#include "cirt/Dialect/FIRRTL/Dialect.h"
#include "cirt/Dialect/RTL/Dialect.h"
#include "cirt/EmitVerilog.h"
#include "cirt/FIRParser.h"
#include "mlir/Dialect/StandardOps/IR/Ops.h"
@ -48,6 +49,9 @@ int main(int argc, char **argv) {
registerMLIRContextCLOptions();
registerDialect<StandardOpsDialect>();
// RTL.
registerDialect<rtl::RTLDialect>();
// Register FIRRTL stuff.
registerDialect<firrtl::FIRRTLDialect>();
registerFIRParserTranslation();

View File

@ -6,6 +6,7 @@
//===----------------------------------------------------------------------===//
#include "cirt/Dialect/FIRRTL/Dialect.h"
#include "cirt/Dialect/RTL/Dialect.h"
#include "cirt/EmitVerilog.h"
#include "cirt/FIRParser.h"
#include "mlir/Dialect/StandardOps/IR/Ops.h"
@ -120,8 +121,9 @@ int main(int argc, char **argv) {
registerMLIRContextCLOptions();
registerPassManagerCLOptions();
// Register FIRRTL stuff.
// Register our dialects.
registerDialect<firrtl::FIRRTLDialect>();
registerDialect<rtl::RTLDialect>();
// Parse pass names in main to ensure static initialization completed.
cl::ParseCommandLineOptions(argc, argv, "cirt modular optimizer driver\n");