Skip to content

Commit

Permalink
[SV Dialect] Implement Visitor and EmitVerilog support for SV.
Browse files Browse the repository at this point in the history
  • Loading branch information
lattner committed Aug 15, 2020
1 parent 485f6ce commit a25a0b4
Show file tree
Hide file tree
Showing 5 changed files with 248 additions and 3 deletions.
12 changes: 12 additions & 0 deletions include/circt/Dialect/SV/Statements.td
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,10 @@ def IfDefOp : SVOp<"ifdef", [HasRegionTerminator]>, HasCustomParserPrinter {

// TODO: ODS doesn't support parsing/printing regions yet :-(
// let assemblyFormat = [{ $clock attr-dict $body }];*/

let extraClassDeclaration = [{
Block *getBodyBlock() { return &body().front(); }
}];
}

def IfOp : SVOp<"if", [HasRegionTerminator]>,
Expand All @@ -41,6 +45,10 @@ def IfOp : SVOp<"if", [HasRegionTerminator]>,

// TODO: ODS doesn't support parsing/printing regions yet :-(
//let assemblyFormat = [{ $cond attr-dict $body }];

let extraClassDeclaration = [{
Block *getBodyBlock() { return &body().front(); }
}];
}

// TODO: This should be generalized, e.g. with an enum to specify the edge
Expand All @@ -55,6 +63,10 @@ def AlwaysAtPosEdgeOp : SVOp<"alwaysat_posedge", [HasRegionTerminator]>,

// TODO: ODS doesn't support parsing/printing regions yet :-(
//let assemblyFormat = [{ $clock attr-dict $body }];

let extraClassDeclaration = [{
Block *getBodyBlock() { return &body().front(); }
}];
}

//===----------------------------------------------------------------------===//
Expand Down
69 changes: 69 additions & 0 deletions include/circt/Dialect/SV/Visitors.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
//===- SV/Visitors.h - SV Dialect Visitors ----------------------*- C++ -*-===//
//
// This file defines visitors that make it easier to work with SV IR.
//
//===----------------------------------------------------------------------===//

#ifndef CIRCT_DIALECT_SV_VISITORS_H
#define CIRCT_DIALECT_SV_VISITORS_H

#include "circt/Dialect/SV/Ops.h"
#include "llvm/ADT/TypeSwitch.h"

namespace circt {
namespace sv {

template <typename ConcreteType, typename ResultType = void,
typename... ExtraArgs>
class Visitor {
public:
ResultType dispatchSVVisitor(Operation *op, ExtraArgs... args) {
auto *thisCast = static_cast<ConcreteType *>(this);
return TypeSwitch<Operation *, ResultType>(op)
.template Case<TextualValueOp,
// Control flow.
IfDefOp, IfOp, AlwaysAtPosEdgeOp,
// Other Statements.
YieldOp, FWriteOp>([&](auto expr) -> ResultType {
return thisCast->visitSV(expr, args...);
})
.Default([&](auto expr) -> ResultType {
return thisCast->visitInvalidSV(op, args...);
});
}

/// This callback is invoked on any invalid operations.
ResultType visitInvalidSV(Operation *op, ExtraArgs... args) {
op->emitOpError("unknown SV node");
abort();
}

/// This callback is invoked on any SV operations that are not handled by the
/// concrete visitor.
ResultType visitUnhandledSV(Operation *op, ExtraArgs... args) {
return ResultType();
}

#define HANDLE(OPTYPE, OPKIND) \
ResultType visitSV(OPTYPE op, ExtraArgs... args) { \
return static_cast<ConcreteType *>(this)->visit##OPKIND##SV(op, args...); \
}

// Expressions
HANDLE(TextualValueOp, Unhandled)

// Control flow.
HANDLE(IfDefOp, Unhandled);
HANDLE(IfOp, Unhandled);
HANDLE(AlwaysAtPosEdgeOp, Unhandled);

// Other Statements.
HANDLE(YieldOp, Unhandled);
HANDLE(FWriteOp, Unhandled);
#undef HANDLE
};

} // namespace sv
} // namespace circt

#endif // CIRCT_DIALECT_SV_VISITORS_H
133 changes: 130 additions & 3 deletions lib/EmitVerilog/EmitVerilog.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
#include "circt/Dialect/FIRRTL/Visitors.h"
#include "circt/Dialect/RTL/Ops.h"
#include "circt/Dialect/RTL/Visitors.h"
#include "circt/Dialect/SV/Ops.h"
#include "circt/Dialect/SV/Visitors.h"
#include "circt/Support/LLVM.h"
#include "mlir/IR/Module.h"
#include "mlir/IR/StandardTypes.h"
Expand Down Expand Up @@ -37,7 +39,8 @@ static llvm::ManagedStatic<StringSet<>> reservedWordCache;
static bool isVerilogExpression(Operation *op) {
// All FIRRTL expressions and RTL combinatorial logic ops are Verilog
// expressions.
return isExpression(op) || rtl::isCombinatorial(op);
return isExpression(op) || rtl::isCombinatorial(op) ||
isa<sv::TextualValueOp>(op);
}

/// Return the width of the specified FIRRTL type in bits or -1 if it isn't
Expand Down Expand Up @@ -302,6 +305,10 @@ class ModuleEmitter : public VerilogEmitterBase {
void emitStatement(rtl::ConnectOp op);
void emitStatement(PrintFOp op);
void emitStatement(StopOp op);
void emitStatement(sv::IfDefOp op);
void emitStatement(sv::IfOp op);
void emitStatement(sv::AlwaysAtPosEdgeOp op);
void emitStatement(sv::FWriteOp op);
void emitDecl(NodeOp op);
void emitDecl(InstanceOp op);
void emitDecl(RegOp op);
Expand Down Expand Up @@ -671,7 +678,8 @@ namespace {
/// stuff, then pre-insert parentheses and other things if we find out that it
/// was needed later.
class ExprEmitter : public ExprVisitor<ExprEmitter, SubExprInfo>,
public rtl::CombinatorialVisitor<ExprEmitter, SubExprInfo> {
public rtl::CombinatorialVisitor<ExprEmitter, SubExprInfo>,
public sv::Visitor<ExprEmitter, SubExprInfo> {
public:
/// Create an ExprEmitter for the specified module emitter, and keeping track
/// of any emitted expressions in the specified set.
Expand All @@ -684,6 +692,7 @@ class ExprEmitter : public ExprVisitor<ExprEmitter, SubExprInfo>,
std::string emitExpressionToString(Value exp, VerilogPrecedence precedence);
friend class ExprVisitor;
friend class CombinatorialVisitor;
friend class Visitor;

/// Do a best-effort job of looking through noop cast operations.
Value lookThroughNoopCasts(Value value) {
Expand All @@ -704,12 +713,14 @@ class ExprEmitter : public ExprVisitor<ExprEmitter, SubExprInfo>,
SubExprInfo visitInvalidExpr(Operation *op) {
return dispatchCombinatorialVisitor(op);
}
SubExprInfo visitInvalidComb(Operation *op) { return visitUnhandledExpr(op); }
SubExprInfo visitInvalidComb(Operation *op) { return dispatchSVVisitor(op); }
SubExprInfo visitUnhandledComb(Operation *op) {
return visitUnhandledExpr(op);
}
SubExprInfo visitUnhandledSV(Operation *op) { return visitUnhandledExpr(op); }

using ExprVisitor::visitExpr;
using Visitor::visitSV;
SubExprInfo visitExpr(firrtl::ConstantOp op);

/// Emit a verilog concatenation of the specified values. If the before or
Expand Down Expand Up @@ -743,6 +754,8 @@ class ExprEmitter : public ExprVisitor<ExprEmitter, SubExprInfo>,
return emitSubExpr(op->getOperand(0), LowestPrecedence);
}

SubExprInfo visitSV(sv::TextualValueOp op);

SubExprInfo visitExpr(AddPrimOp op) {
return emitVariadic(op, Addition, "+");
}
Expand Down Expand Up @@ -1088,6 +1101,11 @@ SubExprInfo ExprEmitter::emitBitSelect(Value operand, unsigned hiBit,
return {Unary, IsUnsigned};
}

SubExprInfo ExprEmitter::visitSV(sv::TextualValueOp op) {
os << op.string();
return {Unary, IsUnsigned};
}

SubExprInfo ExprEmitter::visitExpr(firrtl::ConstantOp op) {
auto resType = op.getType().cast<IntType>();
if (resType.getWidthOrSentinel() == -1)
Expand Down Expand Up @@ -1395,6 +1413,93 @@ void ModuleEmitter::emitStatement(StopOp op) {
addAtPosEdge(action, locInfo, clockExpr, "!SYNTHESIS", condExpr);
}

void ModuleEmitter::emitStatement(sv::FWriteOp op) {
SmallPtrSet<Operation *, 8> ops;
ops.insert(op);

indent() << "$fwrite(32'h80000002, \"";
os.write_escaped(op.string());
os << "\");";
emitLocationInfoAndNewLine(ops);
}

void ModuleEmitter::emitStatement(sv::IfDefOp op) {
auto cond = op.cond();

if (cond.startswith("!"))
indent() << "#ifndef " << cond.drop_front(1);
else
indent() << "#ifdef " << cond;

SmallPtrSet<Operation *, 8> ops;
ops.insert(op);
emitLocationInfoAndNewLine(ops);

addIndent();
auto *block = op.getBodyBlock();
auto it = block->begin(), end = std::prev(block->end());
for (; it != end; ++it)
emitOperation(&*it);
reduceIndent();

indent() << "#endif\n";
}

/// Emit the body of a control flow statement that is surrounded by begin/end
/// markers if non-singular. If the control flow construct is multi-line and
/// if multiLineComment is non-null, the string is included in a comment after
/// the 'end' to make it easier to associate.
static void emitBeginEndRegion(Block *block,
SmallPtrSet<Operation *, 8> &locationOps,
ModuleEmitter &emitter,
const char *multiLineComment = nullptr) {
auto isSingleVerilogStatement = [&](Operation &op) {
// Not all expressions and statements are guaranteed to emit a single
// Verilog statement (for the purposes of if statements). Just do a simple
// check here for now. This can be improved over time.
return isa<sv::FWriteOp>(op);
};

// Get the range of statements we want to emit, ignoring the Yield terminator.
auto it = block->begin(), end = std::prev(block->end());

// Determine if we can omit the begin/end keywords.
bool hasOneStmt =
it != end && std::next(it) == end && isSingleVerilogStatement(*it);
if (!hasOneStmt)
emitter.os << " begin";
emitter.emitLocationInfoAndNewLine(locationOps);

emitter.addIndent();
for (; it != end; ++it)
emitter.emitOperation(&*it);
emitter.reduceIndent();

if (!hasOneStmt) {
emitter.indent() << "end";
if (multiLineComment)
emitter.os << " // " << multiLineComment;
emitter.os << '\n';
}
}

void ModuleEmitter::emitStatement(sv::IfOp op) {
SmallPtrSet<Operation *, 8> ops;
ops.insert(op);

indent() << "if (" << emitExpressionToString(op.cond(), ops) << ')';
emitBeginEndRegion(op.getBodyBlock(), ops, *this);
}

void ModuleEmitter::emitStatement(sv::AlwaysAtPosEdgeOp op) {
SmallPtrSet<Operation *, 8> ops;
ops.insert(op);

indent() << "always @(posedge " << emitExpressionToString(op.clock(), ops)
<< ")";
emitBeginEndRegion(op.getBodyBlock(), ops, *this, "always @(posedge)");
}

void ModuleEmitter::emitDecl(NodeOp op) {
SmallPtrSet<Operation *, 8> ops;
ops.insert(op);
Expand Down Expand Up @@ -1920,6 +2025,28 @@ void ModuleEmitter::emitOperation(Operation *op) {
if (RTLStmtEmitter(*this).dispatchStmtVisitor(op))
return;

class SVEmitter : public sv::Visitor<SVEmitter, bool> {
public:
SVEmitter(ModuleEmitter &emitter) : emitter(emitter) {}

using Visitor::visitSV;
bool visitSV(sv::IfDefOp op) { return emitter.emitStatement(op), true; }
bool visitSV(sv::IfOp op) { return emitter.emitStatement(op), true; }
bool visitSV(sv::AlwaysAtPosEdgeOp op) {
return emitter.emitStatement(op), true;
}
bool visitSV(sv::FWriteOp op) { return emitter.emitStatement(op), true; }

bool visitUnhandledSV(Operation *op) { return false; }
bool visitInvalidSV(Operation *op) { return false; }

private:
ModuleEmitter &emitter;
};

if (SVEmitter(*this).dispatchSVVisitor(op))
return;

emitOpError(op, "cannot emit this operation to Verilog");
indent() << "unknown MLIR operation " << op->getName().getStringRef() << "\n";
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,3 +76,5 @@ firrtl.circuit "Circuit" {
// CHECK-NEXT:endmodule

}


35 changes: 35 additions & 0 deletions test/EmitVerilog/sv-dialect.mlir
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
// RUN: circt-translate %s -emit-verilog -verify-diagnostics | FileCheck %s --strict-whitespace

firrtl.circuit "Circuit" {
// CHECK-LABEL: module M1(
firrtl.module @M1(%clock : i1, %cond : i1) {

sv.alwaysat_posedge %clock {
sv.ifdef "!SYNTHESIS" {
%tmp = sv.textual_value "PRINTF_COND_" : i1
%tmp2 = rtl.and %tmp, %cond : i1
sv.if %tmp2 {
sv.fwrite "Hi\n"
}
}
}

// CHECK: always @(posedge clock) begin
// CHECK-NEXT: #ifndef SYNTHESIS
// CHECK-NEXT: if (PRINTF_COND_ & cond)
// CHECK-NEXT: $fwrite(32'h80000002, "Hi\n");
// CHECK-NEXT: #endif
// CHECK-NEXT: end // always @(posedge)
sv.if %cond {
sv.fwrite "Hi\n"
sv.fwrite "Bye\n"
}

// CHECK-NEXT: if (cond) begin
// CHECK-NEXT: $fwrite(32'h80000002, "Hi\n");
// CHECK-NEXT: $fwrite(32'h80000002, "Bye\n");
// CHECK-NEXT: end
}
}


0 comments on commit a25a0b4

Please sign in to comment.