mirror of https://github.com/llvm/circt.git
[ImportVerilog] Convert initial/always/final procedures (#6766)
Add the `moore.procedure` operation to represent `initial`, `final`, `always`, `always_comb`, `always_latch`, and `always_ff` procedures. Extend the ImportVerilog conversion to map the corresponding Slang AST nodes to these new operations. These procedures expect a valid body statement or statement block. In order to write basic tests, and in preparation for statement conversion in the future, add the `Statements.cpp` file with a `StmtVisitor` that handles the conversion of Verilog statements to MLIR ops. A new `convertStatement` function on the conversion context acts as entry point to convert statements, similar to `convertModule*` or `convertType`. We only support a few basic statements as of now, mainly stray semicolons, nested blocks, and variable declarations. This commit explicitly does not handle variable lifetime yet, which is a topic for a future refinement PR. Thanks @hailongSun2000 for doing the heavy lifting here and getting this going. Co-authored-by: Hailong Sun <hailong.sun@terapines.com>
This commit is contained in:
parent
2d588384a2
commit
0749166b43
|
@ -73,6 +73,69 @@ def InstanceOp : MooreOp<"instance", [
|
|||
}];
|
||||
}
|
||||
|
||||
def Initial: I32EnumAttrCase<"Initial", 0, "initial">;
|
||||
def Final: I32EnumAttrCase<"Final", 1, "final">;
|
||||
def Always: I32EnumAttrCase<"Always", 2, "always">;
|
||||
def AlwaysComb: I32EnumAttrCase<"AlwaysComb", 3, "always_comb">;
|
||||
def AlwaysLatch: I32EnumAttrCase<"AlwaysLatch", 4, "always_latch">;
|
||||
def AlwaysFF: I32EnumAttrCase<"AlwaysFF", 5, "always_ff">;
|
||||
|
||||
def ProcedureKindAttr: I32EnumAttr<"ProcedureKind", "Procedure kind",
|
||||
[Initial, Final, Always, AlwaysComb, AlwaysLatch, AlwaysFF]>{
|
||||
let cppNamespace = "circt::moore";
|
||||
}
|
||||
|
||||
def ProcedureOp : MooreOp<"procedure", [
|
||||
SingleBlock,
|
||||
NoTerminator,
|
||||
NoRegionArguments,
|
||||
RecursiveMemoryEffects,
|
||||
RecursivelySpeculatable
|
||||
]> {
|
||||
let summary = "A procedure executed at different points in time";
|
||||
let description = [{
|
||||
The `moore.procedure` operation represents the SystemVerilog `initial`,
|
||||
`final`, `always`, `always_comb`, `always_latch`, and `always_ff`
|
||||
procedures.
|
||||
|
||||
Execution times of the various procedures:
|
||||
|
||||
- An `initial` procedure is executed once at the start of a design's
|
||||
lifetime, before any other procedures are executed.
|
||||
|
||||
- A `final` procedure is executed once at the end of a design's lifetime,
|
||||
after all other procedures have stopped execution.
|
||||
|
||||
- An `always` or `always_ff` procedure is repeatedly executed during a
|
||||
design's lifetime. Timing and event control inside the procedure can
|
||||
suspend its execution, for example to wait for a signal to change. If no
|
||||
such timing or event control is present, the procedure repeats infinitely
|
||||
at the current timestep, effectively deadlocking the design.
|
||||
|
||||
- An `always_comb` or `always_latch` procedure is executed once at the start
|
||||
of a design's lifetime, after any `initial` procedures, and throughout the
|
||||
lifetime of the design whenever any of the variables read by the body of
|
||||
the procedure changes. Since the procedure is only executed when its
|
||||
change, and not repeatedly, the body generally does not contain any timing
|
||||
or event control. This behavior mitigates a shortcoming of `always`
|
||||
procedures, which commonly have an event control like `@*` that blocks
|
||||
and waits for a change of any input signals. This prevents the body from
|
||||
executing when the design is initialized and properly reacting to the
|
||||
initial values of signals. In contrast, `always_comb` and `always_latch`
|
||||
procedures have an implicit unconditional execution at design start-up.
|
||||
|
||||
See IEEE 1800-2017 § 9.2 "Structured procedures".
|
||||
}];
|
||||
|
||||
let regions = (region SizedRegion<1>:$bodyRegion);
|
||||
let arguments = (ins ProcedureKindAttr:$kind);
|
||||
let results = (outs);
|
||||
|
||||
let assemblyFormat = [{
|
||||
$kind attr-dict-with-keyword $bodyRegion
|
||||
}];
|
||||
}
|
||||
|
||||
//===----------------------------------------------------------------------===//
|
||||
// Declarations
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
|
|
@ -30,6 +30,7 @@ endif ()
|
|||
|
||||
add_circt_translation_library(CIRCTImportVerilog
|
||||
ImportVerilog.cpp
|
||||
Statements.cpp
|
||||
Structure.cpp
|
||||
Types.cpp
|
||||
|
||||
|
|
|
@ -54,6 +54,9 @@ struct Context {
|
|||
convertModuleHeader(const slang::ast::InstanceBodySymbol *module);
|
||||
LogicalResult convertModuleBody(const slang::ast::InstanceBodySymbol *module);
|
||||
|
||||
// Convert a statement AST node to MLIR ops.
|
||||
LogicalResult convertStatement(const slang::ast::Statement *statement);
|
||||
|
||||
mlir::ModuleOp intoModuleOp;
|
||||
const slang::SourceManager &sourceManager;
|
||||
SmallDenseMap<slang::BufferID, StringRef> &bufferFilePaths;
|
||||
|
|
|
@ -0,0 +1,90 @@
|
|||
//===- Statements.cpp - Slang statement conversion ------------------------===//
|
||||
//
|
||||
// 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
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
#include "ImportVerilogInternals.h"
|
||||
|
||||
using namespace circt;
|
||||
using namespace ImportVerilog;
|
||||
|
||||
// NOLINTBEGIN(misc-no-recursion)
|
||||
namespace {
|
||||
struct StmtVisitor {
|
||||
Context &context;
|
||||
Location loc;
|
||||
OpBuilder &builder;
|
||||
|
||||
StmtVisitor(Context &context, Location loc)
|
||||
: context(context), loc(loc), builder(context.builder) {}
|
||||
|
||||
// Skip empty statements (stray semicolons).
|
||||
LogicalResult visit(const slang::ast::EmptyStatement &) { return success(); }
|
||||
|
||||
// Convert every statement in a statement list. The Verilog syntax follows a
|
||||
// similar philosophy as C/C++, where things like `if` and `for` accept a
|
||||
// single statement as body. But then a `{...}` block is a valid statement,
|
||||
// which allows for the `if {...}` syntax. In Verilog, things like `final`
|
||||
// accept a single body statement, but that can be a `begin ... end` block,
|
||||
// which in turn has a single body statement, which then commonly is a list of
|
||||
// statements.
|
||||
LogicalResult visit(const slang::ast::StatementList &stmts) {
|
||||
for (auto *stmt : stmts.list)
|
||||
if (failed(stmt->visit(*this)))
|
||||
return failure();
|
||||
return success();
|
||||
}
|
||||
|
||||
// Inline `begin ... end` blocks into the parent.
|
||||
LogicalResult visit(const slang::ast::BlockStatement &stmt) {
|
||||
return stmt.body.visit(*this);
|
||||
}
|
||||
|
||||
// Handle variable declarations.
|
||||
LogicalResult visit(const slang::ast::VariableDeclStatement &stmt) {
|
||||
const auto &var = stmt.symbol;
|
||||
auto type = context.convertType(*var.getDeclaredType());
|
||||
if (!type)
|
||||
return failure();
|
||||
|
||||
Value initial;
|
||||
if (const auto *init = var.getInitializer()) {
|
||||
return mlir::emitError(loc,
|
||||
"variable initializer expressions not supported");
|
||||
}
|
||||
|
||||
builder.create<moore::VariableOp>(loc, type,
|
||||
builder.getStringAttr(var.name), initial);
|
||||
return success();
|
||||
}
|
||||
|
||||
// Ignore timing control for now.
|
||||
LogicalResult visit(const slang::ast::TimedStatement &stmt) {
|
||||
return success();
|
||||
}
|
||||
|
||||
/// Emit an error for all other statements.
|
||||
template <typename T>
|
||||
LogicalResult visit(T &&stmt) {
|
||||
mlir::emitError(loc, "unsupported statement: ")
|
||||
<< slang::ast::toString(stmt.kind);
|
||||
return mlir::failure();
|
||||
}
|
||||
|
||||
LogicalResult visitInvalid(const slang::ast::Statement &stmt) {
|
||||
mlir::emitError(loc, "invalid statement: ")
|
||||
<< slang::ast::toString(stmt.kind);
|
||||
return mlir::failure();
|
||||
}
|
||||
};
|
||||
} // namespace
|
||||
|
||||
LogicalResult
|
||||
Context::convertStatement(const slang::ast::Statement *statement) {
|
||||
auto loc = convertLocation(statement->sourceRange.start());
|
||||
return statement->visit(StmtVisitor(*this, loc));
|
||||
}
|
||||
// NOLINTEND(misc-no-recursion)
|
|
@ -16,6 +16,25 @@ using namespace ImportVerilog;
|
|||
// Module Member Conversion
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
static moore::ProcedureKind
|
||||
convertProcedureKind(slang::ast::ProceduralBlockKind kind) {
|
||||
switch (kind) {
|
||||
case slang::ast::ProceduralBlockKind::Always:
|
||||
return moore::ProcedureKind::Always;
|
||||
case slang::ast::ProceduralBlockKind::AlwaysComb:
|
||||
return moore::ProcedureKind::AlwaysComb;
|
||||
case slang::ast::ProceduralBlockKind::AlwaysLatch:
|
||||
return moore::ProcedureKind::AlwaysLatch;
|
||||
case slang::ast::ProceduralBlockKind::AlwaysFF:
|
||||
return moore::ProcedureKind::AlwaysFF;
|
||||
case slang::ast::ProceduralBlockKind::Initial:
|
||||
return moore::ProcedureKind::Initial;
|
||||
case slang::ast::ProceduralBlockKind::Final:
|
||||
return moore::ProcedureKind::Final;
|
||||
}
|
||||
llvm_unreachable("all procedure kinds handled");
|
||||
}
|
||||
|
||||
namespace {
|
||||
struct MemberVisitor {
|
||||
Context &context;
|
||||
|
@ -69,6 +88,26 @@ struct MemberVisitor {
|
|||
return success();
|
||||
}
|
||||
|
||||
// Handle procedures.
|
||||
LogicalResult visit(const slang::ast::ProceduralBlockSymbol &procNode) {
|
||||
auto procOp = builder.create<moore::ProcedureOp>(
|
||||
loc, convertProcedureKind(procNode.procedureKind));
|
||||
procOp.getBodyRegion().emplaceBlock();
|
||||
OpBuilder::InsertionGuard guard(builder);
|
||||
builder.setInsertionPointToEnd(procOp.getBody());
|
||||
return context.convertStatement(&procNode.getBody());
|
||||
}
|
||||
|
||||
// Ignore statement block symbols. These get generated by Slang for blocks
|
||||
// with variables and other declarations. For example, having an initial
|
||||
// procedure with a variable declaration, such as `initial begin int x; end`,
|
||||
// will create the procedure with a block and variable declaration as
|
||||
// expected, but will also create a `StatementBlockSymbol` with just the
|
||||
// variable layout _next to_ the initial procedure.
|
||||
LogicalResult visit(const slang::ast::StatementBlockSymbol &) {
|
||||
return success();
|
||||
}
|
||||
|
||||
/// Emit an error for all other members.
|
||||
template <typename T>
|
||||
LogicalResult visit(T &&node) {
|
||||
|
|
|
@ -7,14 +7,7 @@
|
|||
//===----------------------------------------------------------------------===//
|
||||
|
||||
#include "ImportVerilogInternals.h"
|
||||
#include "slang/ast/ASTVisitor.h"
|
||||
#include "slang/ast/Symbol.h"
|
||||
#include "slang/ast/symbols/CompilationUnitSymbols.h"
|
||||
#include "slang/ast/symbols/InstanceSymbols.h"
|
||||
#include "slang/ast/symbols/VariableSymbols.h"
|
||||
#include "slang/ast/types/AllTypes.h"
|
||||
#include "slang/ast/types/Type.h"
|
||||
#include "slang/syntax/SyntaxVisitor.h"
|
||||
#include "slang/syntax/AllSyntax.h"
|
||||
|
||||
using namespace circt;
|
||||
using namespace ImportVerilog;
|
||||
|
|
|
@ -37,8 +37,39 @@ module Parent;
|
|||
Child child();
|
||||
endmodule
|
||||
|
||||
// CHECK-LABEL: moore.module @Foo
|
||||
module Foo;
|
||||
// CHECK-LABEL: moore.module @Basic
|
||||
module Basic;
|
||||
// CHECK: %myVar = moore.variable
|
||||
var myVar;
|
||||
|
||||
// CHECK: moore.procedure initial {
|
||||
// CHECK: }
|
||||
initial;
|
||||
|
||||
// CHECK: moore.procedure final {
|
||||
// CHECK: }
|
||||
final begin end
|
||||
|
||||
// CHECK: moore.procedure always {
|
||||
// CHECK: %x = moore.variable
|
||||
// CHECK: %y = moore.variable
|
||||
// CHECK: }
|
||||
always begin
|
||||
int x;
|
||||
begin
|
||||
int y;
|
||||
end
|
||||
end
|
||||
|
||||
// CHECK: moore.procedure always_comb {
|
||||
// CHECK: }
|
||||
always_comb begin end
|
||||
|
||||
// CHECK: moore.procedure always_latch {
|
||||
// CHECK: }
|
||||
always_latch begin end
|
||||
|
||||
// CHECK: moore.procedure always_ff {
|
||||
// CHECK: }
|
||||
always_ff @* begin end
|
||||
endmodule
|
||||
|
|
|
@ -57,3 +57,22 @@ module Foo;
|
|||
// expected-error @below {{variable initializer expressions not supported}}
|
||||
int a = 0;
|
||||
endmodule
|
||||
|
||||
// -----
|
||||
|
||||
module Foo;
|
||||
initial begin
|
||||
// expected-error @below {{variable initializer expressions not supported}}
|
||||
automatic int a = 0;
|
||||
end
|
||||
endmodule
|
||||
|
||||
// -----
|
||||
|
||||
module Foo;
|
||||
int a;
|
||||
initial begin
|
||||
// expected-error @below {{unsupported statement}}
|
||||
release a;
|
||||
end
|
||||
endmodule
|
||||
|
|
|
@ -6,8 +6,21 @@ moore.module @Foo {
|
|||
moore.instance "foo" @Foo
|
||||
// CHECK: %myVar = moore.variable : !moore.bit
|
||||
%myVar = moore.variable : !moore.bit
|
||||
// CHECK: [[TMP:%.+]] = moore.variable name "myVar" : !moore.bit
|
||||
moore.variable name "myVar" : !moore.bit
|
||||
// CHECK: [[TMP:%.+]] = moore.variable name "myVar" %myVar : !moore.bit
|
||||
moore.variable name "myVar" %myVar : !moore.bit
|
||||
|
||||
// CHECK: moore.procedure initial {
|
||||
// CHECK: moore.procedure final {
|
||||
// CHECK: moore.procedure always {
|
||||
// CHECK: moore.procedure always_comb {
|
||||
// CHECK: moore.procedure always_latch {
|
||||
// CHECK: moore.procedure always_ff {
|
||||
moore.procedure initial {}
|
||||
moore.procedure final {}
|
||||
moore.procedure always {}
|
||||
moore.procedure always_comb {}
|
||||
moore.procedure always_latch {}
|
||||
moore.procedure always_ff {}
|
||||
}
|
||||
|
||||
// CHECK-LABEL: moore.module @Bar
|
||||
|
|
Loading…
Reference in New Issue