[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:
Fabian Schuiki 2024-03-02 20:55:36 -08:00 committed by GitHub
parent 2b68d16b04
commit 5c9fb8f3a7
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 229 additions and 26 deletions

View File

@ -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

View File

@ -29,6 +29,7 @@ else ()
endif ()
add_circt_translation_library(CIRCTImportVerilog
Expressions.cpp
ImportVerilog.cpp
Statements.cpp
Structure.cpp

View File

@ -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)

View File

@ -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.

View File

@ -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

View File

@ -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)

View File

@ -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))))

View File

@ -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

View File

@ -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
// -----

View File

@ -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