Always_ff support with explicit resets and reset style (sync/async) (#491)

Always_ff support with explicit resets and reset style (sync/async).  Reset blocks are tracked as part of this node to simplify merging always blocks with similar style resets.  Keeping different always blocks for different reset styles of registers is driven from tool requirements and style guidelines.
This commit is contained in:
Andrew Lenharth 2021-01-22 14:27:35 -06:00 committed by GitHub
parent 51fb3ad5e4
commit ce5241c4fb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 263 additions and 41 deletions

View File

@ -138,6 +138,60 @@ def AlwaysOp : SVOp<"always", [HasRegionTerminator, NoRegionArguments,
let verifier = "return ::verifyAlwaysOp(*this);";
}
def NoReset: I32EnumAttrCase<"NoReset", 0, "noreset">;
def SyncReset: I32EnumAttrCase<"SyncReset", 1, "syncreset">;
def AsyncReset: I32EnumAttrCase<"AsyncReset", 2, "asyncreset">;
def ResetTypeAttr : I32EnumAttr<"ResetType", "reset type",
[NoReset, SyncReset, AsyncReset]>;
def AlwaysFFOp : SVOp<"alwaysff", [HasRegionTerminator, NoRegionArguments,
RecursiveSideEffects]> {
let summary = "'alwaysff @' block with optional reset";
let description = [{
alwaysff blocks represent always_ff verilog nodes, which enforce inference
of registers. This block takes a clock signal and edge sensitivity and
reset type. If the reset type is anything but 'noreset', the block takes a
reset signal, reset sensitivity, and reset block. Appropriate if conditions
are generated in the output code based on the reset type. A negative-edge,
asynchronous reset will check the inverse of the reset condition
(if (!reset) begin resetblock end) to match the sensitivity.
}];
let regions = (region SizedRegion<1>:$bodyBlk, AnyRegion:$resetBlk);
let arguments = (ins EventControlAttr:$clockEdge, I1:$clock,
DefaultValuedAttr<ResetTypeAttr, "0">:$resetStyle,
OptionalAttr<EventControlAttr>:$resetEdge,
Optional<I1>:$reset);
let results = (outs);
let assemblyFormat = [{
$clockEdge `,` $clock
( `,` $resetStyle `,` $resetEdge^ `,` $reset $resetBlk )? $bodyBlk attr-dict
}];
let skipDefaultBuilders = 1;
let builders = [
OpBuilderDAG<(ins "EventControl":$clockEdge, "Value":$clock,
CArg<"std::function<void()>", "{}">:$bodyCtor)>,
OpBuilderDAG<(ins "EventControl":$clockEdge, "Value":$clock,
"ResetType":$resetStyle,
"EventControl":$resetEdge, "Value":$reset,
CArg<"std::function<void()>", "{}">:$resetCtor,
CArg<"std::function<void()>", "{}">:$bodyCtor)>
];
let extraClassDeclaration = [{
Block *getBodyBlock() { return &bodyBlk().front(); }
Block *getResetBlock() { return &resetBlk().front(); }
}];
// Check that we have the same number of events and conditions.
//let verifier = "return ::verifyAlwaysOp(*this);";
}
def InitialOp : SVOp<"initial", [HasRegionTerminator, NoRegionArguments,
RecursiveSideEffects]> {
let summary = "'initial' block";
@ -167,7 +221,8 @@ def InitialOp : SVOp<"initial", [HasRegionTerminator, NoRegionArguments,
def YieldOp
: SVOp<"yield", [NoSideEffect, Terminator,
ParentOneOf<["IfDefOp, IfOp", "AlwaysOp", "InitialOp"]>]> {
ParentOneOf<["IfDefOp, IfOp", "AlwaysOp", "InitialOp",
"AlwaysFFOp"]>]> {
let summary = "terminator for control-flow operation regions";
let arguments = (ins);

View File

@ -30,7 +30,7 @@ public:
// Declarations.
RegOp,
// Control flow.
IfDefOp, IfOp, AlwaysOp, InitialOp,
IfDefOp, IfOp, AlwaysOp, AlwaysFFOp, InitialOp,
// Other Statements.
YieldOp, BPAssignOp, PAssignOp, AliasOp, FWriteOp,
FatalOp, FinishOp, VerbatimOp,
@ -75,6 +75,7 @@ public:
HANDLE(IfDefOp, Unhandled);
HANDLE(IfOp, Unhandled);
HANDLE(AlwaysOp, Unhandled);
HANDLE(AlwaysFFOp, Unhandled);
HANDLE(InitialOp, Unhandled);
// Other Statements.

View File

@ -1941,13 +1941,13 @@ LogicalResult FIRRTLLowering::visitStmt(ConnectOp op) {
if (!clockVal)
return failure();
builder->create<sv::AlwaysOp>(EventControl::AtPosEdge, clockVal, [&]() {
builder->create<sv::AlwaysFFOp>(EventControl::AtPosEdge, clockVal, [&]() {
builder->create<sv::PAssignOp>(destVal, srcVal);
});
return success();
}
// If this is an assignment to a RegInit, then the connect implicitly
// If this is an assignment to a RegReset, then the connect implicitly
// happens under the clock and reset that gate the register.
if (auto regResetOp = dyn_cast_or_null<RegResetOp>(dest.getDefiningOp())) {
Value clockVal = getLoweredValue(regResetOp.clockVal());
@ -1955,24 +1955,16 @@ LogicalResult FIRRTLLowering::visitStmt(ConnectOp op) {
if (!clockVal || !resetSignal)
return failure();
auto one = builder->create<rtl::ConstantOp>(APInt(1, 1));
auto invResetSignal = builder->create<rtl::XorOp>(resetSignal, one);
// auto one = builder->create<rtl::ConstantOp>(APInt(1, 1));
// auto invResetSignal = builder->create<rtl::XorOp>(resetSignal, one);
auto resetFn = [&]() {
builder->create<sv::IfOp>(invResetSignal, [&]() {
builder->create<sv::PAssignOp>(destVal, srcVal);
});
};
if (regResetOp.resetSignal().getType().isa<AsyncResetType>()) {
builder->create<sv::AlwaysOp>(
ArrayRef<EventControl>{EventControl::AtPosEdge,
EventControl::AtPosEdge},
ArrayRef<Value>{clockVal, resetSignal}, resetFn);
return success();
} else { // sync reset
builder->create<sv::AlwaysOp>(EventControl::AtPosEdge, clockVal, resetFn);
}
builder->create<sv::AlwaysFFOp>(
EventControl::AtPosEdge, clockVal,
regResetOp.resetSignal().getType().isa<AsyncResetType>()
? ::ResetType::AsyncReset
: ::ResetType::SyncReset,
EventControl::AtPosEdge, resetSignal, std::function<void()>(),
[&]() { builder->create<sv::PAssignOp>(destVal, srcVal); });
return success();
}

View File

@ -245,8 +245,74 @@ static void printEventList(OpAsmPrinter &p, AlwaysOp op, ArrayAttr portsAttr,
}
}
//===----------------------------------------------------------------------===//
// AlwaysFFOp
void AlwaysFFOp::build(OpBuilder &odsBuilder, OperationState &result,
EventControl clockEdge, Value clock,
std::function<void()> bodyCtor) {
result.addAttribute("clockEdge", odsBuilder.getI32IntegerAttr(
static_cast<int32_t>(clockEdge)));
result.addOperands(clock);
result.addAttribute(
"resetStyle",
odsBuilder.getI32IntegerAttr(static_cast<int32_t>(ResetType::NoReset)));
// Set up the body.
Region *bodyBlk = result.addRegion();
AlwaysFFOp::ensureTerminator(*bodyBlk, odsBuilder, result.location);
if (bodyCtor) {
auto oldIP = &*odsBuilder.getInsertionPoint();
odsBuilder.setInsertionPointToStart(&*bodyBlk->begin());
bodyCtor();
odsBuilder.setInsertionPoint(oldIP);
}
// Set up the reset
Region *resetBlk = result.addRegion();
}
void AlwaysFFOp::build(OpBuilder &odsBuilder, OperationState &result,
EventControl clockEdge, Value clock,
ResetType resetStyle, EventControl resetEdge,
Value reset, std::function<void()> resetCtor,
std::function<void()> bodyCtor) {
result.addAttribute("clockEdge", odsBuilder.getI32IntegerAttr(
static_cast<int32_t>(clockEdge)));
result.addOperands(clock);
result.addAttribute("resetStyle", odsBuilder.getI32IntegerAttr(
static_cast<int32_t>(resetStyle)));
result.addAttribute("resetEdge", odsBuilder.getI32IntegerAttr(
static_cast<int32_t>(resetEdge)));
result.addOperands(reset);
// Set up the body.
Region *bodyRegion = result.addRegion();
if (bodyCtor) {
AlwaysFFOp::ensureTerminator(*bodyRegion, odsBuilder, result.location);
auto oldIP = &*odsBuilder.getInsertionPoint();
odsBuilder.setInsertionPointToStart(&*bodyRegion->begin());
bodyCtor();
odsBuilder.setInsertionPoint(oldIP);
}
// Set up the reset.
Region *resetRegion = result.addRegion();
if (resetCtor) {
AlwaysFFOp::ensureTerminator(*resetRegion, odsBuilder, result.location);
auto oldIP = &*odsBuilder.getInsertionPoint();
odsBuilder.setInsertionPointToStart(&*resetRegion->begin());
resetCtor();
odsBuilder.setInsertionPoint(oldIP);
}
}
//===----------------------------------------------------------------------===//
// InitialOp
//===----------------------------------------------------------------------===//
void InitialOp::build(OpBuilder &odsBuilder, OperationState &result,
std::function<void()> bodyCtor) {
@ -261,7 +327,6 @@ void InitialOp::build(OpBuilder &odsBuilder, OperationState &result,
odsBuilder.setInsertionPoint(oldIP);
}
}
//===----------------------------------------------------------------------===//
// TypeDecl operations
//===----------------------------------------------------------------------===//

View File

@ -312,6 +312,7 @@ public:
LogicalResult visitSV(IfDefOp op);
LogicalResult visitSV(IfOp op);
LogicalResult visitSV(AlwaysOp op);
LogicalResult visitSV(AlwaysFFOp op);
LogicalResult visitSV(InitialOp op);
LogicalResult visitSV(FWriteOp op);
LogicalResult visitSV(FatalOp op);
@ -1443,6 +1444,56 @@ LogicalResult ModuleEmitter::visitSV(AlwaysOp op) {
return success();
}
LogicalResult ModuleEmitter::visitSV(AlwaysFFOp op) {
SmallPtrSet<Operation *, 8> ops;
ops.insert(op);
indent() << "always_ff @(" << stringifyEventControl(op.clockEdge()) << " "
<< emitExpressionToString(op.clock(), ops);
if (op.resetStyle() == ResetType::AsyncReset) {
os << " or " << stringifyEventControl(*op.resetEdge()) << " "
<< emitExpressionToString(op.reset(), ops);
}
os << ')';
// Build the comment string, leave out the signal expressions (since they
// can be large).
std::string comment;
comment = "always_ff @(";
comment += stringifyEventControl(op.clockEdge());
if (op.resetStyle() == ResetType::AsyncReset) {
comment += " or ";
comment += stringifyEventControl(*op.resetEdge());
}
comment += ')';
if (op.resetStyle() == ResetType::NoReset)
emitBeginEndRegion(op.getBodyBlock(), ops, *this, comment);
else {
os << " begin";
emitLocationInfoAndNewLine(ops);
addIndent();
indent() << "if (";
// Negative edge async resets need to invert the reset condition. This is
// noted in the op description.
if (op.resetStyle() == ResetType::AsyncReset &&
*op.resetEdge() == EventControl::AtNegEdge)
os << "!";
os << emitExpressionToString(op.reset(), ops) << ')';
emitBeginEndRegion(op.getResetBlock(), ops, *this);
indent() << "else";
emitBeginEndRegion(op.getBodyBlock(), ops, *this);
reduceIndent();
indent() << "end";
os << " // " << comment;
os << '\n';
}
return success();
}
LogicalResult ModuleEmitter::visitSV(InitialOp op) {
SmallPtrSet<Operation *, 8> ops;
ops.insert(op);

View File

@ -479,7 +479,7 @@ module attributes {firrtl.mainModule = "Simple"} {
%4 = firrtl.mux(%2, %3, %count) : (!firrtl.uint<1>, !firrtl.uint<2>, !firrtl.uint<2>) -> !firrtl.uint<2>
%5 = firrtl.mux(%1, %c0_ui2, %4) : (!firrtl.uint<1>, !firrtl.uint<2>, !firrtl.uint<2>) -> !firrtl.uint<2>
// CHECK-NEXT: sv.always posedge %clock {
// CHECK-NEXT: sv.alwaysff posedge, %clock {
// CHECK-NEXT: sv.passign %count, %2 : i2
// CHECK-NEXT: }
firrtl.connect %count, %5 : !firrtl.uint<2>, !firrtl.uint<2>
@ -522,11 +522,11 @@ module attributes {firrtl.mainModule = "Simple"} {
// CHECK-NEXT: sv.initial {
// CHECK-NEXT: sv.verbatim "`INIT_RANDOM_PROLOG_"
// CHECK-NEXT: sv.ifdef "RANDOMIZE_REG_INIT" {
// CHECK-NEXT: %true_1 = rtl.constant(true) : i1
// CHECK-NEXT: %9 = rtl.xor %reset, %true_1 : i1
// CHECK-NEXT: sv.if %9 {
// CHECK-NEXT: %10 = sv.textual_value "`RANDOM" : i32
// CHECK-NEXT: sv.bpassign %reg, %10 : i32
// CHECK-NEXT: %true = rtl.constant(true) : i1
// CHECK-NEXT: %8 = rtl.xor %reset, %true : i1
// CHECK-NEXT: sv.if %8 {
// CHECK-NEXT: %9 = sv.textual_value "`RANDOM" : i32
// CHECK-NEXT: sv.bpassign %reg, %9 : i32
// CHECK-NEXT: }
// CHECK-NEXT: }
// CHECK-NEXT: }
@ -540,11 +540,11 @@ module attributes {firrtl.mainModule = "Simple"} {
// CHECK-NEXT: sv.ifdef "!SYNTHESIS" {
// CHECK-NEXT: sv.initial {
// CHECK-NEXT: sv.ifdef "RANDOMIZE_REG_INIT" {
// CHECK-NEXT: %true_1 = rtl.constant(true) : i1
// CHECK-NEXT: %9 = rtl.xor %reset, %true_1 : i1
// CHECK-NEXT: sv.if %9 {
// CHECK-NEXT: %10 = sv.textual_value "`RANDOM" : i32
// CHECK-NEXT: sv.bpassign %reg2, %10 : i32
// CHECK-NEXT: %true = rtl.constant(true) : i1
// CHECK-NEXT: %8 = rtl.xor %reset, %true : i1
// CHECK-NEXT: sv.if %8 {
// CHECK-NEXT: %9 = sv.textual_value "`RANDOM" : i32
// CHECK-NEXT: sv.bpassign %reg2, %9 : i32
// CHECK-NEXT: }
// CHECK-NEXT: }
// CHECK-NEXT: }
@ -565,18 +565,15 @@ module attributes {firrtl.mainModule = "Simple"} {
%shorten = firrtl.head %sum, 32 : (!firrtl.uint<33>) -> !firrtl.uint<32>
%5 = firrtl.mux(%3, %2, %shorten) : (!firrtl.uint<1>, !firrtl.uint<32>, !firrtl.uint<32>) -> !firrtl.uint<32>
// CHECK-NEXT: %true = rtl.constant(true) : i1
// CHECK-NEXT: %7 = rtl.xor %reset, %true : i1
// CHECK-NEXT: sv.always posedge %clock, posedge %reset {
// CHECK-NEXT: sv.if %7 {
// CHECK-NEXT: sv.passign %reg, %6 : i32
// CHECK-NEXT: }
// CHECK-NEXT: sv.alwaysff posedge, %clock, asyncreset, posedge, %reset {
// CHECK-NEXT: } {
// CHECK-NEXT: sv.passign %reg, %6 : i32
// CHECK-NEXT: }
firrtl.connect %reg, %5 : !firrtl.uint<32>, !firrtl.uint<32>
%6 = firrtl.stdIntCast %reg : (!firrtl.uint<32>) -> i32
// CHECK-NEXT: %8 = rtl.read_inout %reg : !rtl.inout<i32>
// CHECK-NEXT: rtl.output %8 : i32
// CHECK-NEXT: %7 = rtl.read_inout %reg : !rtl.inout<i32>
// CHECK-NEXT: rtl.output %7 : i32
rtl.output %6 : i32
}

View File

@ -42,6 +42,37 @@ func @test1(%arg0: i1, %arg1: i1) {
// CHECK-NEXT: }
// CHECK-NEXT: }
sv.alwaysff "posedge", %arg0 {
sv.fwrite "Yo\n"
}
// CHECK-NEXT: sv.alwaysff posedge, %arg0 {
// CHECK-NEXT: sv.fwrite "Yo\0A"
// CHECK-NEXT: }
sv.alwaysff "posedge", %arg0, syncreset, "posedge", %arg1 {
sv.fwrite "Sync Reset Block\n"
} {
sv.fwrite "Sync Main Block\n"
}
// CHECK-NEXT: sv.alwaysff posedge, %arg0, syncreset, posedge, %arg1 {
// CHECK-NEXT: sv.fwrite "Sync Reset Block\0A"
// CHECK-NEXT: } {
// CHECK-NEXT: sv.fwrite "Sync Main Block\0A"
// CHECK-NEXT: }
sv.alwaysff "posedge", %arg0, asyncreset, "negedge", %arg1 {
sv.fwrite "Async Reset Block\n"
} {
sv.fwrite "Async Main Block\n"
}
// CHECK-NEXT: sv.alwaysff posedge, %arg0, asyncreset, negedge, %arg1 {
// CHECK-NEXT: sv.fwrite "Async Reset Block\0A"
// CHECK-NEXT: } {
// CHECK-NEXT: sv.fwrite "Async Main Block\0A"
// CHECK-NEXT: }
// Smoke test generic syntax.
"sv.if"(%arg0) ( {

View File

@ -40,6 +40,36 @@ rtl.module @M1(%clock : i1, %cond : i1, %val : i8) {
sv.always posedge %clock, negedge %cond {
}
// CHECK-NEXT: always_ff @(posedge clock)
// CHECK-NEXT: $fwrite(32'h80000002, "Yo\n");
sv.alwaysff "posedge", %clock {
sv.fwrite "Yo\n"
}
// CHECK-NEXT: always_ff @(posedge clock) begin
// CHECK-NEXT: if (cond)
// CHECK-NEXT: $fwrite(32'h80000002, "Sync Reset Block\n")
// CHECK-NEXT: else
// CHECK-NEXT: $fwrite(32'h80000002, "Sync Main Block\n");
// CHECK-NEXT: end // always_ff @(posedge)
sv.alwaysff "posedge", %clock, syncreset, "posedge", %cond {
sv.fwrite "Sync Reset Block\n"
} {
sv.fwrite "Sync Main Block\n"
}
// CHECK-NEXT: always_ff @(posedge clock or negedge cond) begin
// CHECK-NEXT: if (!cond)
// CHECK-NEXT: $fwrite(32'h80000002, "Async Reset Block\n");
// CHECK-NEXT: else
// CHECK-NEXT: $fwrite(32'h80000002, "Async Main Block\n");
// CHECK-NEXT: end // always_ff @(posedge or negedge)
sv.alwaysff "posedge", %clock, asyncreset, "negedge", %cond {
sv.fwrite "Async Reset Block\n"
} {
sv.fwrite "Async Main Block\n"
}
%c42 = rtl.constant (42 : i42) : i42
// CHECK-NEXT: if (cond)