[ImportVerilog] Add option to lower always @* as always_comb (#8271)

Add the `--always-at-star-as-comb` option to circt-verilog and add a
corresponding option to ImportVerilog. When this option is set, detect
`always @*` in the Verilog AST and lower it as a `always_comb`. This is
a common pattern in synthesizers and simulators, since the traiditonal
`always @*` in Verilog does not accurately describe the behaviour of
combinational logic.

Enable this option by default, which seems to be a common behaviour
among existing tools.
This commit is contained in:
Fabian Schuiki 2025-02-25 20:16:37 -08:00 committed by GitHub
parent 3f0aa622f0
commit d02a452a23
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 90 additions and 35 deletions

View File

@ -49,6 +49,9 @@ struct ImportVerilogOptions {
/// Generate debug information in the form of debug dialect ops in the IR.
bool debugInfo = false;
/// Interpret `always @(*)` as `always_comb`.
bool lowerAlwaysAtStarAsComb = true;
//===--------------------------------------------------------------------===//
// Include paths
//===--------------------------------------------------------------------===//

View File

@ -505,19 +505,34 @@ struct ModuleVisitor : public BaseVisitor {
}
// Handle procedures.
LogicalResult visit(const slang::ast::ProceduralBlockSymbol &procNode) {
auto procOp = builder.create<moore::ProcedureOp>(
loc, convertProcedureKind(procNode.procedureKind));
LogicalResult convertProcedure(moore::ProcedureKind kind,
const slang::ast::Statement &body) {
auto procOp = builder.create<moore::ProcedureOp>(loc, kind);
OpBuilder::InsertionGuard guard(builder);
builder.setInsertionPointToEnd(&procOp.getBody().emplaceBlock());
Context::ValueSymbolScope scope(context.valueSymbols);
if (failed(context.convertStatement(procNode.getBody())))
if (failed(context.convertStatement(body)))
return failure();
if (builder.getBlock())
builder.create<moore::ReturnOp>(loc);
return success();
}
LogicalResult visit(const slang::ast::ProceduralBlockSymbol &procNode) {
// Detect `always @(*) <stmt>` and convert to `always_comb <stmt>` if
// requested by the user.
if (context.options.lowerAlwaysAtStarAsComb) {
auto *stmt = procNode.getBody().as_if<slang::ast::TimedStatement>();
if (procNode.procedureKind == slang::ast::ProceduralBlockKind::Always &&
stmt &&
stmt->timing.kind == slang::ast::TimingControlKind::ImplicitEvent)
return convertProcedure(moore::ProcedureKind::AlwaysComb, stmt->stmt);
}
return convertProcedure(convertProcedureKind(procNode.procedureKind),
procNode.getBody());
}
// Handle generate block.
LogicalResult visit(const slang::ast::GenerateBlockSymbol &genNode) {
if (!genNode.isUninstantiated) {

View File

@ -132,37 +132,6 @@ module Basic;
bit [0:0] b1;
bit b2 = b1;
// 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
// CHECK: [[TMP1:%.+]] = moore.read %v2
// CHECK: moore.assign %v1, [[TMP1]] : i32
assign v1 = v2;

View File

@ -0,0 +1,62 @@
// RUN: circt-verilog --parse-only --always-at-star-as-comb=0 %s | FileCheck %s --check-prefixes=CHECK,CHECK-STAR
// RUN: circt-verilog --parse-only --always-at-star-as-comb=1 %s | FileCheck %s --check-prefixes=CHECK,CHECK-COMB
// REQUIRES: slang
// Internal issue in Slang v3 about jump depending on uninitialised value.
// UNSUPPORTED: valgrind
// CHECK-LABEL: moore.module @Foo()
module Foo;
// CHECK: moore.procedure initial {
// CHECK-NEXT: func.call @foo
// CHECK-NEXT: moore.return
// CHECK-NEXT: }
initial foo();
// CHECK: moore.procedure final {
// CHECK-NEXT: func.call @foo
// CHECK-NEXT: moore.return
// CHECK-NEXT: }
final foo();
// CHECK: moore.procedure always {
// CHECK-NEXT: func.call @foo
// CHECK-NEXT: moore.return
// CHECK-NEXT: }
always foo();
// CHECK: moore.procedure always_comb {
// CHECK-NEXT: func.call @foo
// CHECK-NEXT: moore.return
// CHECK-NEXT: }
always_comb foo();
// CHECK: moore.procedure always_latch {
// CHECK-NEXT: func.call @foo
// CHECK-NEXT: moore.return
// CHECK-NEXT: }
always_latch foo();
// CHECK: moore.procedure always_ff {
// CHECK-NEXT: moore.wait_event {
// CHECK-NEXT: }
// CHECK-NEXT: func.call @foo
// CHECK-NEXT: moore.return
// CHECK-NEXT: }
always_ff @* foo();
// CHECK-STAR: moore.procedure always {
// CHECK-STAR-NEXT: moore.wait_event {
// CHECK-STAR-NEXT: }
// CHECK-STAR-NEXT: func.call @foo
// CHECK-STAR-NEXT: moore.return
// CHECK-STAR-NEXT: }
// CHECK-COMB: moore.procedure always_comb {
// CHECK-COMB-NEXT: func.call @foo
// CHECK-COMB-NEXT: moore.return
// CHECK-COMB-NEXT: }
always @* foo();
endmodule
function void foo();
endfunction

View File

@ -117,6 +117,11 @@ struct CLOptions {
cl::opt<bool> debugInfo{"g", cl::desc("Generate debug information"),
cl::cat(cat)};
cl::opt<bool> lowerAlwaysAtStarAsComb{
"always-at-star-as-comb",
cl::desc("Interpret `always @(*)` as `always_comb`"), cl::init(true),
cl::cat(cat)};
//===--------------------------------------------------------------------===//
// Include paths
//===--------------------------------------------------------------------===//
@ -357,6 +362,7 @@ static LogicalResult executeWithSources(MLIRContext *context,
else if (opts.loweringMode == LoweringMode::OnlyParse)
options.mode = ImportVerilogOptions::Mode::OnlyParse;
options.debugInfo = opts.debugInfo;
options.lowerAlwaysAtStarAsComb = opts.lowerAlwaysAtStarAsComb;
options.includeDirs = opts.includeDirs;
options.includeSystemDirs = opts.includeSystemDirs;