mirror of https://github.com/llvm/circt.git
[ImportVerilog] Add assignment statements (#6773)
Add continuous, blocking, and non-blocking assignments to the Moore dialect. These represent the corresponding constructs in SystemVerilog. Add a `ScopedHashTable` to the ImportVerilog conversion context and add variable declarations to it. This allows expression conversion to resolve name expressions to the MLIR value that was generated for the corresponding declaration. The Slang AST node pointer itself is used as a lookup key, since the AST already has all names resolved. Add a basic expression handling mechanism to the ImportVerilog conversion. A new `convertExpression` entry point calls an `ExprVisitor` to convert expression nodes in the Slang AST to the corresponding MLIR operations. This currently only supports name expressions, which simply looks up the converted MLIR value for whatever the name was resolved to, and blocking and non-blocking assignments. Extend the module conversion to properly handle continuous assignments at the module level. Thanks @albertethon, @hailongSun2000, and @angelzzzzz for the help to get this started. Co-authored-by: ShiZuoye <albertethon@163.com> Co-authored-by: Hailong Sun <hailong.sun@terapines.com> Co-authored-by: Anqi Yu <anqi.yu@terapines.com>
This commit is contained in:
parent
2b68d16b04
commit
5c9fb8f3a7
|
@ -157,4 +157,51 @@ def VariableOp : MooreOp<"variable", [
|
|||
}];
|
||||
}
|
||||
|
||||
//===----------------------------------------------------------------------===//
|
||||
// Assignments
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
class AssignOpBase<string mnemonic, list<Trait> traits = []> :
|
||||
MooreOp<mnemonic, traits # [SameTypeOperands]> {
|
||||
let arguments = (ins AnyType:$dst, AnyType:$src);
|
||||
let assemblyFormat = [{
|
||||
$dst `,` $src attr-dict `:` type($src)
|
||||
}];
|
||||
}
|
||||
|
||||
def ContinuousAssignOp : AssignOpBase<"assign", [HasParent<"SVModuleOp">]> {
|
||||
let summary = "Continuous assignment within a module";
|
||||
let description = [{
|
||||
A continuous assignment in module scope, such as `assign x = y;`, which
|
||||
continuously drives the value on the right-hand side onto the left-hand
|
||||
side.
|
||||
|
||||
See IEEE 1800-2017 § 10.3 "Continuous assignments".
|
||||
}];
|
||||
}
|
||||
|
||||
def BlockingAssignOp : AssignOpBase<"blocking_assign"> {
|
||||
let summary = "Blocking procedural assignment";
|
||||
let description = [{
|
||||
A blocking procedural assignment in a sequential block, such as `x = y`. The
|
||||
effects of the assignment are visible to any subsequent operations in the
|
||||
block.
|
||||
|
||||
See IEEE 1800-2017 § 10.4.1 "Blocking procedural assignments".
|
||||
}];
|
||||
}
|
||||
|
||||
def NonBlockingAssignOp : AssignOpBase<"nonblocking_assign"> {
|
||||
let summary = "Nonblocking procedural assignment";
|
||||
let description = [{
|
||||
A nonblocking procedural assignment in a sequential block, such as `x <= y;`
|
||||
or `x <= @(posedge y) z` or `x <= #1ns y`. The assignment does not take
|
||||
effect immediately. Subsequent operations in the block do not see the
|
||||
effects of this assignment. Instead, the assignment is scheduled to happen
|
||||
in a subsequent time step as dictated by the delay or event control.
|
||||
|
||||
See IEEE 1800-2017 § 10.4.2 "Nonblocking procedural assignments".
|
||||
}];
|
||||
}
|
||||
|
||||
#endif // CIRCT_DIALECT_MOORE_MOOREOPS
|
||||
|
|
|
@ -29,6 +29,7 @@ else ()
|
|||
endif ()
|
||||
|
||||
add_circt_translation_library(CIRCTImportVerilog
|
||||
Expressions.cpp
|
||||
ImportVerilog.cpp
|
||||
Statements.cpp
|
||||
Structure.cpp
|
||||
|
|
|
@ -0,0 +1,74 @@
|
|||
//===- Expressions.cpp - Slang expression 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"
|
||||
#include "slang/syntax/AllSyntax.h"
|
||||
|
||||
using namespace circt;
|
||||
using namespace ImportVerilog;
|
||||
|
||||
// NOLINTBEGIN(misc-no-recursion)
|
||||
namespace {
|
||||
struct ExprVisitor {
|
||||
Context &context;
|
||||
Location loc;
|
||||
OpBuilder &builder;
|
||||
|
||||
ExprVisitor(Context &context, Location loc)
|
||||
: context(context), loc(loc), builder(context.builder) {}
|
||||
|
||||
// Handle named values, such as references to declared variables.
|
||||
Value visit(const slang::ast::NamedValueExpression &expr) {
|
||||
if (auto value = context.valueSymbols.lookup(&expr.symbol))
|
||||
return value;
|
||||
auto d = mlir::emitError(loc, "unknown name `") << expr.symbol.name << "`";
|
||||
d.attachNote(context.convertLocation(expr.symbol.location))
|
||||
<< "no value generated for " << slang::ast::toString(expr.symbol.kind);
|
||||
return {};
|
||||
}
|
||||
|
||||
// Handle blocking and non-blocking assignments.
|
||||
Value visit(const slang::ast::AssignmentExpression &expr) {
|
||||
auto lhs = context.convertExpression(expr.left());
|
||||
auto rhs = context.convertExpression(expr.right());
|
||||
if (!lhs || !rhs)
|
||||
return {};
|
||||
|
||||
if (expr.timingControl) {
|
||||
auto loc = context.convertLocation(expr.timingControl->sourceRange);
|
||||
mlir::emitError(loc, "delayed assignments not supported");
|
||||
return {};
|
||||
}
|
||||
|
||||
if (expr.isNonBlocking())
|
||||
builder.create<moore::NonBlockingAssignOp>(loc, lhs, rhs);
|
||||
else
|
||||
builder.create<moore::BlockingAssignOp>(loc, lhs, rhs);
|
||||
return lhs;
|
||||
}
|
||||
|
||||
/// Emit an error for all other expressions.
|
||||
template <typename T>
|
||||
Value visit(T &&node) {
|
||||
mlir::emitError(loc, "unsupported expression: ")
|
||||
<< slang::ast::toString(node.kind);
|
||||
return {};
|
||||
}
|
||||
|
||||
Value visitInvalid(const slang::ast::Expression &expr) {
|
||||
mlir::emitError(loc, "invalid expression");
|
||||
return {};
|
||||
}
|
||||
};
|
||||
} // namespace
|
||||
|
||||
Value Context::convertExpression(const slang::ast::Expression &expr) {
|
||||
auto loc = convertLocation(expr.sourceRange);
|
||||
return expr.visit(ExprVisitor(*this, loc));
|
||||
}
|
||||
// NOLINTEND(misc-no-recursion)
|
|
@ -64,6 +64,10 @@ Location Context::convertLocation(slang::SourceLocation loc) {
|
|||
return ::convertLocation(getContext(), sourceManager, bufferFilePaths, loc);
|
||||
}
|
||||
|
||||
Location Context::convertLocation(slang::SourceRange range) {
|
||||
return convertLocation(range.start());
|
||||
}
|
||||
|
||||
namespace {
|
||||
/// A converter that can be plugged into a slang `DiagnosticEngine` as a client
|
||||
/// that will map slang diagnostics to their MLIR counterpart and emit them.
|
||||
|
|
|
@ -13,6 +13,7 @@
|
|||
#include "circt/Conversion/ImportVerilog.h"
|
||||
#include "circt/Dialect/Moore/MooreOps.h"
|
||||
#include "slang/ast/ASTVisitor.h"
|
||||
#include "llvm/ADT/ScopedHashTable.h"
|
||||
#include "llvm/Support/Debug.h"
|
||||
#include <map>
|
||||
#include <queue>
|
||||
|
@ -40,6 +41,8 @@ struct Context {
|
|||
|
||||
/// Convert a slang `SourceLocation` into an MLIR `Location`.
|
||||
Location convertLocation(slang::SourceLocation loc);
|
||||
/// Convert a slang `SourceRange` into an MLIR `Location`.
|
||||
Location convertLocation(slang::SourceRange range);
|
||||
|
||||
/// Convert a slang type into an MLIR type. Returns null on failure. Uses the
|
||||
/// provided location for error reporting, or tries to guess one from the
|
||||
|
@ -55,7 +58,10 @@ struct Context {
|
|||
LogicalResult convertModuleBody(const slang::ast::InstanceBodySymbol *module);
|
||||
|
||||
// Convert a statement AST node to MLIR ops.
|
||||
LogicalResult convertStatement(const slang::ast::Statement *statement);
|
||||
LogicalResult convertStatement(const slang::ast::Statement &stmt);
|
||||
|
||||
// Convert an expression AST node to MLIR ops.
|
||||
Value convertExpression(const slang::ast::Expression &expr);
|
||||
|
||||
mlir::ModuleOp intoModuleOp;
|
||||
const slang::SourceManager &sourceManager;
|
||||
|
@ -74,6 +80,14 @@ struct Context {
|
|||
/// A list of modules for which the header has been created, but the body has
|
||||
/// not been converted yet.
|
||||
std::queue<const slang::ast::InstanceBodySymbol *> moduleWorklist;
|
||||
|
||||
/// A table of defined values, such as variables, that may be referred to by
|
||||
/// name in expressions. The expressions use this table to lookup the MLIR
|
||||
/// value that was created for a given declaration in the Slang AST node.
|
||||
using ValueSymbols =
|
||||
llvm::ScopedHashTable<const slang::ast::ValueSymbol *, Value>;
|
||||
using ValueSymbolScope = ValueSymbols::ScopeTy;
|
||||
ValueSymbols valueSymbols;
|
||||
};
|
||||
|
||||
} // namespace ImportVerilog
|
||||
|
|
|
@ -33,14 +33,19 @@ struct StmtVisitor {
|
|||
// statements.
|
||||
LogicalResult visit(const slang::ast::StatementList &stmts) {
|
||||
for (auto *stmt : stmts.list)
|
||||
if (failed(stmt->visit(*this)))
|
||||
if (failed(context.convertStatement(*stmt)))
|
||||
return failure();
|
||||
return success();
|
||||
}
|
||||
|
||||
// Inline `begin ... end` blocks into the parent.
|
||||
LogicalResult visit(const slang::ast::BlockStatement &stmt) {
|
||||
return stmt.body.visit(*this);
|
||||
return context.convertStatement(stmt.body);
|
||||
}
|
||||
|
||||
// Handle expression statements.
|
||||
LogicalResult visit(const slang::ast::ExpressionStatement &stmt) {
|
||||
return success(context.convertExpression(stmt.expr));
|
||||
}
|
||||
|
||||
// Handle variable declarations.
|
||||
|
@ -52,8 +57,9 @@ struct StmtVisitor {
|
|||
|
||||
Value initial;
|
||||
if (const auto *init = var.getInitializer()) {
|
||||
return mlir::emitError(loc,
|
||||
"variable initializer expressions not supported");
|
||||
initial = context.convertExpression(*init);
|
||||
if (!initial)
|
||||
return failure();
|
||||
}
|
||||
|
||||
builder.create<moore::VariableOp>(loc, type,
|
||||
|
@ -82,9 +88,8 @@ struct StmtVisitor {
|
|||
};
|
||||
} // namespace
|
||||
|
||||
LogicalResult
|
||||
Context::convertStatement(const slang::ast::Statement *statement) {
|
||||
auto loc = convertLocation(statement->sourceRange.start());
|
||||
return statement->visit(StmtVisitor(*this, loc));
|
||||
LogicalResult Context::convertStatement(const slang::ast::Statement &stmt) {
|
||||
auto loc = convertLocation(stmt.sourceRange);
|
||||
return stmt.visit(StmtVisitor(*this, loc));
|
||||
}
|
||||
// NOLINTEND(misc-no-recursion)
|
||||
|
|
|
@ -78,13 +78,35 @@ struct MemberVisitor {
|
|||
return failure();
|
||||
|
||||
Value initial;
|
||||
if (const auto *initNode = varNode.getInitializer()) {
|
||||
return mlir::emitError(loc,
|
||||
"variable initializer expressions not supported");
|
||||
if (const auto *init = varNode.getInitializer()) {
|
||||
initial = context.convertExpression(*init);
|
||||
if (!initial)
|
||||
return failure();
|
||||
}
|
||||
|
||||
builder.create<moore::VariableOp>(
|
||||
auto varOp = builder.create<moore::VariableOp>(
|
||||
loc, type, builder.getStringAttr(varNode.name), initial);
|
||||
context.valueSymbols.insert(&varNode, varOp);
|
||||
return success();
|
||||
}
|
||||
|
||||
// Handle continuous assignments.
|
||||
LogicalResult visit(const slang::ast::ContinuousAssignSymbol &assignNode) {
|
||||
if (const auto *delay = assignNode.getDelay()) {
|
||||
auto loc = context.convertLocation(delay->sourceRange);
|
||||
return mlir::emitError(loc,
|
||||
"delayed continuous assignments not supported");
|
||||
}
|
||||
|
||||
const auto &expr =
|
||||
assignNode.getAssignment().as<slang::ast::AssignmentExpression>();
|
||||
|
||||
auto lhs = context.convertExpression(expr.left());
|
||||
auto rhs = context.convertExpression(expr.right());
|
||||
if (!lhs || !rhs)
|
||||
return failure();
|
||||
|
||||
builder.create<moore::ContinuousAssignOp>(loc, lhs, rhs);
|
||||
return success();
|
||||
}
|
||||
|
||||
|
@ -95,7 +117,8 @@ struct MemberVisitor {
|
|||
procOp.getBodyRegion().emplaceBlock();
|
||||
OpBuilder::InsertionGuard guard(builder);
|
||||
builder.setInsertionPointToEnd(procOp.getBody());
|
||||
return context.convertStatement(&procNode.getBody());
|
||||
Context::ValueSymbolScope scope(context.valueSymbols);
|
||||
return context.convertStatement(procNode.getBody());
|
||||
}
|
||||
|
||||
// Ignore statement block symbols. These get generated by Slang for blocks
|
||||
|
@ -217,6 +240,7 @@ Context::convertModuleBody(const slang::ast::InstanceBodySymbol *module) {
|
|||
OpBuilder::InsertionGuard g(builder);
|
||||
builder.setInsertionPointToEnd(moduleOp.getBody());
|
||||
|
||||
ValueSymbolScope scope(valueSymbols);
|
||||
for (auto &member : module->members()) {
|
||||
auto loc = convertLocation(member.location);
|
||||
if (failed(member.visit(MemberVisitor(*this, loc))))
|
||||
|
|
|
@ -39,8 +39,12 @@ endmodule
|
|||
|
||||
// CHECK-LABEL: moore.module @Basic
|
||||
module Basic;
|
||||
// CHECK: %myVar = moore.variable
|
||||
var myVar;
|
||||
// CHECK: %v0 = moore.variable : !moore.logic
|
||||
// CHECK: %v1 = moore.variable : !moore.int
|
||||
// CHECK: %v2 = moore.variable %v1 : !moore.int
|
||||
var v0;
|
||||
int v1;
|
||||
int v2 = v1;
|
||||
|
||||
// CHECK: moore.procedure initial {
|
||||
// CHECK: }
|
||||
|
@ -72,4 +76,23 @@ module Basic;
|
|||
// CHECK: moore.procedure always_ff {
|
||||
// CHECK: }
|
||||
always_ff @* begin end
|
||||
|
||||
// CHECK: moore.assign %v1, %v2 : !moore.int
|
||||
assign v1 = v2;
|
||||
endmodule
|
||||
|
||||
// CHECK-LABEL: moore.module @Statements
|
||||
module Statements;
|
||||
bit x, y, z;
|
||||
initial begin
|
||||
// CHECK: moore.blocking_assign %x, %y : !moore.bit
|
||||
x = y;
|
||||
|
||||
// CHECK: moore.blocking_assign %y, %z : !moore.bit
|
||||
// CHECK: moore.blocking_assign %x, %y : !moore.bit
|
||||
x = (y = z);
|
||||
|
||||
// CHECK: moore.nonblocking_assign %x, %y : !moore.bit
|
||||
x <= y;
|
||||
end
|
||||
endmodule
|
||||
|
|
|
@ -54,17 +54,17 @@ endmodule
|
|||
// -----
|
||||
|
||||
module Foo;
|
||||
// expected-error @below {{variable initializer expressions not supported}}
|
||||
int a = 0;
|
||||
int x;
|
||||
// expected-error @below {{delayed assignments not supported}}
|
||||
initial x <= #1ns x;
|
||||
endmodule
|
||||
|
||||
// -----
|
||||
|
||||
module Foo;
|
||||
initial begin
|
||||
// expected-error @below {{variable initializer expressions not supported}}
|
||||
automatic int a = 0;
|
||||
end
|
||||
int x;
|
||||
// expected-error @below {{delayed continuous assignments not supported}}
|
||||
assign #1ns x = x;
|
||||
endmodule
|
||||
|
||||
// -----
|
||||
|
|
|
@ -4,10 +4,11 @@
|
|||
moore.module @Foo {
|
||||
// CHECK: moore.instance "foo" @Foo
|
||||
moore.instance "foo" @Foo
|
||||
// CHECK: %myVar = moore.variable : !moore.bit
|
||||
%myVar = moore.variable : !moore.bit
|
||||
// CHECK: [[TMP:%.+]] = moore.variable name "myVar" %myVar : !moore.bit
|
||||
moore.variable name "myVar" %myVar : !moore.bit
|
||||
// CHECK: %v1 = moore.variable : !moore.bit
|
||||
%v1 = moore.variable : !moore.bit
|
||||
%v2 = moore.variable : !moore.bit
|
||||
// CHECK: [[TMP:%.+]] = moore.variable name "v1" %v2 : !moore.bit
|
||||
moore.variable name "v1" %v2 : !moore.bit
|
||||
|
||||
// CHECK: moore.procedure initial {
|
||||
// CHECK: moore.procedure final {
|
||||
|
@ -21,6 +22,16 @@ moore.module @Foo {
|
|||
moore.procedure always_comb {}
|
||||
moore.procedure always_latch {}
|
||||
moore.procedure always_ff {}
|
||||
|
||||
// CHECK: moore.assign %v1, %v2 : !moore.bit
|
||||
moore.assign %v1, %v2 : !moore.bit
|
||||
|
||||
moore.procedure always {
|
||||
// CHECK: moore.blocking_assign %v1, %v2 : !moore.bit
|
||||
moore.blocking_assign %v1, %v2 : !moore.bit
|
||||
// CHECK: moore.nonblocking_assign %v1, %v2 : !moore.bit
|
||||
moore.nonblocking_assign %v1, %v2 : !moore.bit
|
||||
}
|
||||
}
|
||||
|
||||
// CHECK-LABEL: moore.module @Bar
|
||||
|
|
Loading…
Reference in New Issue