Skip to content

Commit 1e18df3

Browse files
authored
[FIRRTL][firtool] Add --inline-input-only-modules option to firtool (#9332)
This patch adds support for inlining modules that have only input ports (no hardware output or inout ports). Some synthesis tools treat modules without outputs as blackboxes, which can cause synthesis errors. This option allows users to inline such modules to prevent these issues. The implementation includes a new AnnotateInputOnlyModules pass that identifies modules with only input ports and annotates them with InlineAnnotation. A new --inline-input-only-modules command-line option is added to firtool to enable this feature. The pass only annotates private modules that are instantiated in the effective design. Non-hardware ports such as Probe and Integer are not counted as hardware outputs.
1 parent 13bc57f commit 1e18df3

7 files changed

Lines changed: 227 additions & 1 deletion

File tree

include/circt/Dialect/FIRRTL/Passes.td

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -918,4 +918,21 @@ def LowerDomains : Pass<"firrtl-lower-domains", "firrtl::CircuitOp"> {
918918
];
919919
}
920920

921+
def AnnotateInputOnlyModules : Pass<"firrtl-annotate-input-only-modules",
922+
"firrtl::CircuitOp"> {
923+
let summary = "Annotate input-only modules with InlineAnnotation";
924+
let description = [{
925+
This pass identifies modules that have only input ports (no output or inout
926+
ports) and annotates them with `firrtl.passes.InlineAnnotation`. These
927+
modules don't produce any hardware outputs.
928+
929+
Inlining these modules is necessary because some synthesis tools treat
930+
modules without outputs as blackboxes, which can cause synthesis errors.
931+
}];
932+
let statistics = [
933+
Statistic<"numAnnotated", "num-annotated", "Number of modules annotated">
934+
];
935+
}
936+
937+
921938
#endif // CIRCT_DIALECT_FIRRTL_PASSES_TD

include/circt/Firtool/Firtool.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,8 @@ class FirtoolOptions {
143143

144144
bool getEmitAllBindFiles() const { return emitAllBindFiles; }
145145

146+
bool shouldInlineInputOnlyModules() const { return inlineInputOnlyModules; }
147+
146148
// Setters, used by the CAPI
147149
FirtoolOptions &setOutputFilename(StringRef name) {
148150
outputFilename = name;
@@ -392,6 +394,11 @@ class FirtoolOptions {
392394
return *this;
393395
}
394396

397+
FirtoolOptions &setInlineInputOnlyModules(bool value) {
398+
inlineInputOnlyModules = value;
399+
return *this;
400+
}
401+
395402
private:
396403
std::string outputFilename;
397404

@@ -446,6 +453,7 @@ class FirtoolOptions {
446453
bool lintStaticAsserts;
447454
bool lintXmrsInDesign;
448455
bool emitAllBindFiles;
456+
bool inlineInputOnlyModules;
449457
};
450458

451459
void registerFirtoolCLOptions();
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4+
// See https://llvm.org/LICENSE.txt for license information.
5+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6+
//
7+
//===----------------------------------------------------------------------===//
8+
//
9+
// This file defines the AnnotateInputOnlyModules pass.
10+
//
11+
//===----------------------------------------------------------------------===//
12+
13+
#include "circt/Analysis/FIRRTLInstanceInfo.h"
14+
#include "circt/Dialect/FIRRTL/AnnotationDetails.h"
15+
#include "circt/Dialect/FIRRTL/FIRRTLAnnotations.h"
16+
#include "circt/Dialect/FIRRTL/FIRRTLOps.h"
17+
#include "circt/Dialect/FIRRTL/Passes.h"
18+
#include "mlir/Pass/Pass.h"
19+
20+
#define DEBUG_TYPE "firrtl-annotate-input-only-modules"
21+
22+
namespace circt {
23+
namespace firrtl {
24+
#define GEN_PASS_DEF_ANNOTATEINPUTONLYMODULES
25+
#include "circt/Dialect/FIRRTL/Passes.h.inc"
26+
} // namespace firrtl
27+
} // namespace circt
28+
29+
using namespace circt;
30+
using namespace firrtl;
31+
32+
namespace {
33+
struct AnnotateInputOnlyModulesPass
34+
: public circt::firrtl::impl::AnnotateInputOnlyModulesBase<
35+
AnnotateInputOnlyModulesPass> {
36+
void runOnOperation() override;
37+
38+
LogicalResult initialize(MLIRContext *context) override {
39+
// Cache the inline annotation.
40+
inlineAnno = DictionaryAttr::getWithSorted(
41+
context, {{StringAttr::get(context, "class"),
42+
StringAttr::get(context, inlineAnnoClass)}});
43+
return success();
44+
}
45+
46+
mlir::DictionaryAttr inlineAnno;
47+
};
48+
49+
} // end anonymous namespace
50+
51+
void AnnotateInputOnlyModulesPass::runOnOperation() {
52+
auto circuit = getOperation();
53+
bool changed = false;
54+
auto &instanceInfo = getAnalysis<InstanceInfo>();
55+
for (auto module : circuit.getOps<FModuleOp>()) {
56+
// Input only modules.
57+
if (!instanceInfo.anyInstanceInEffectiveDesign(module) || module.isPublic())
58+
continue;
59+
60+
// Check if the module has only input ports (no output ports)
61+
bool hasHardwareOutputPort =
62+
llvm::any_of(module.getPorts(), [&](auto port) {
63+
return port.direction == Direction::Out &&
64+
type_isa<FIRRTLBaseType>(port.type);
65+
});
66+
67+
// If the module has only input ports, add InlineAnnotation
68+
if (hasHardwareOutputPort)
69+
continue;
70+
71+
AnnotationSet annos(module);
72+
73+
// Check if InlineAnnotation or DontTouchAnnotation exists
74+
if (annos.hasAnnotation(inlineAnnoClass) || annos.hasDontTouch())
75+
continue;
76+
77+
LLVM_DEBUG(llvm::dbgs() << "Annotating inline annotation "
78+
<< module.getModuleName() << "\n");
79+
80+
// Create InlineAnnotation
81+
annos.addAnnotations(ArrayRef<Attribute>{inlineAnno});
82+
annos.applyToOperation(module);
83+
84+
++numAnnotated;
85+
changed = true;
86+
}
87+
88+
if (!changed)
89+
return markAllAnalysesPreserved();
90+
91+
markAnalysesPreserved<igraph::InstanceGraph, InstanceInfo>();
92+
}

lib/Dialect/FIRRTL/Transforms/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
add_circt_dialect_library(CIRCTFIRRTLTransforms
22
AddSeqMemPorts.cpp
3+
AnnotateInputOnlyModules.cpp
34
AssignOutputDirs.cpp
45
BlackBoxReader.cpp
56
CheckCombLoops.cpp

lib/Firtool/Firtool.cpp

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -204,6 +204,13 @@ LogicalResult firtool::populateCHIRRTLToLowFIRRTL(mlir::PassManager &pm,
204204
pm.nest<firrtl::CircuitOp>().nest<firrtl::FModuleOp>().addPass(
205205
createSimpleCanonicalizerPass());
206206
pm.addPass(firrtl::createIMDeadCodeElim());
207+
if (opt.shouldInlineInputOnlyModules()) {
208+
pm.nest<firrtl::CircuitOp>().addPass(
209+
firrtl::createAnnotateInputOnlyModules());
210+
pm.nest<firrtl::CircuitOp>().addPass(firrtl::createInliner());
211+
pm.nest<firrtl::CircuitOp>().nest<firrtl::FModuleOp>().addPass(
212+
createSimpleCanonicalizerPass());
213+
}
207214
}
208215

209216
// Always run this, required for legalization.
@@ -760,6 +767,10 @@ struct FirtoolCmdOptions {
760767
llvm::cl::desc("Emit bindfiles for private modules"),
761768
llvm::cl::init(false)};
762769

770+
llvm::cl::opt<bool> inlineInputOnlyModules{
771+
"inline-input-only-modules", llvm::cl::desc("Inline input-only modules"),
772+
llvm::cl::init(false)};
773+
763774
//===----------------------------------------------------------------------===
764775
// Lint options
765776
//===----------------------------------------------------------------------===
@@ -811,7 +822,8 @@ circt::firtool::FirtoolOptions::FirtoolOptions()
811822
disableCSEinClasses(false), selectDefaultInstanceChoice(false),
812823
symbolicValueLowering(verif::SymbolicValueLowering::ExtModule),
813824
disableWireElimination(false), lintStaticAsserts(true),
814-
lintXmrsInDesign(true), emitAllBindFiles(false) {
825+
lintXmrsInDesign(true), emitAllBindFiles(false),
826+
inlineInputOnlyModules(false) {
815827
if (!clOptions.isConstructed())
816828
return;
817829
outputFilename = clOptions->outputFilename;
@@ -864,4 +876,5 @@ circt::firtool::FirtoolOptions::FirtoolOptions()
864876
lintStaticAsserts = clOptions->lintStaticAsserts;
865877
lintXmrsInDesign = clOptions->lintXmrsInDesign;
866878
emitAllBindFiles = clOptions->emitAllBindFiles;
879+
inlineInputOnlyModules = clOptions->inlineInputOnlyModules;
867880
}
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
// RUN: circt-opt --pass-pipeline='builtin.module(firrtl.circuit(firrtl-annotate-input-only-modules))' %s | FileCheck %s
2+
3+
// Test that modules with non-hardware output ports (Probe, Integer) ARE annotated
4+
firrtl.circuit "ModuleWithNonHardwareOutput" {
5+
// CHECK-LABEL: firrtl.module @ModuleWithNonHardwareOutput
6+
firrtl.module @ModuleWithNonHardwareOutput(in %a: !firrtl.uint<8>, out %p: !firrtl.probe<uint<8>>, out %i: !firrtl.integer) {
7+
%child_x, %child_probe, %child_int = firrtl.instance child @ChildWithNonHardwareOutput(in x: !firrtl.uint<8>, out probe: !firrtl.probe<uint<8>>, out int: !firrtl.integer)
8+
firrtl.matchingconnect %child_x, %a : !firrtl.uint<8>
9+
firrtl.ref.define %p, %child_probe : !firrtl.probe<uint<8>>
10+
firrtl.propassign %i, %child_int : !firrtl.integer
11+
}
12+
13+
// CHECK-LABEL: firrtl.module private @ChildWithNonHardwareOutput
14+
// CHECK-SAME: attributes {annotations = [{class = "firrtl.passes.InlineAnnotation"}]}
15+
firrtl.module private @ChildWithNonHardwareOutput(in %x: !firrtl.uint<8>, out %probe: !firrtl.probe<uint<8>>, out %int: !firrtl.integer) {
16+
%0 = firrtl.ref.send %x : !firrtl.uint<8>
17+
firrtl.ref.define %probe, %0 : !firrtl.probe<uint<8>>
18+
%c42_i = firrtl.integer 42
19+
firrtl.propassign %int, %c42_i : !firrtl.integer
20+
}
21+
}
22+
23+
// Test that modules with existing annotations are preserved
24+
firrtl.circuit "ExistingAnnotations" {
25+
// CHECK-LABEL: firrtl.module @ExistingAnnotations
26+
firrtl.module @ExistingAnnotations(in %a: !firrtl.uint<8>) {
27+
%child_x = firrtl.instance child @ChildWithAnnotation(in x: !firrtl.uint<8>)
28+
firrtl.matchingconnect %child_x, %a : !firrtl.uint<8>
29+
}
30+
31+
// CHECK-LABEL: firrtl.module private @ChildWithAnnotation
32+
// CHECK-SAME: annotations = [{class = "some.other.Annotation"}, {class = "firrtl.passes.InlineAnnotation"}]
33+
firrtl.module private @ChildWithAnnotation(in %x: !firrtl.uint<8>) attributes {annotations = [{class = "some.other.Annotation"}]} {
34+
}
35+
}
36+
37+
// Test public modules are NOT annotated (even if input-only)
38+
firrtl.circuit "PublicInputOnly" {
39+
// CHECK-LABEL: firrtl.module @PublicInputOnly
40+
// CHECK-NOT: InlineAnnotation
41+
firrtl.module @PublicInputOnly(in %a: !firrtl.uint<8>) {
42+
}
43+
}
44+
45+
firrtl.circuit "ModuleWithDontTouch" {
46+
// CHECK-LABEL: firrtl.module @ModuleWithDontTouch
47+
firrtl.module @ModuleWithDontTouch(in %a: !firrtl.uint<8>) {
48+
%child_x = firrtl.instance child @ChildWithNonHardwareOutput(in x: !firrtl.uint<8>)
49+
firrtl.matchingconnect %child_x, %a : !firrtl.uint<8>
50+
}
51+
52+
// CHECK-LABEL: firrtl.module private @ChildWithNonHardwareOutput
53+
// CHECK-NOT: InlineAnnotation
54+
firrtl.module private @ChildWithNonHardwareOutput(in %x: !firrtl.uint<8>) attributes {annotations = [{class = "firrtl.transforms.DontTouchAnnotation"}]} {}
55+
}
56+
57+
firrtl.circuit "Bound" {
58+
// CHECK-LABEL: firrtl.module @Bound
59+
firrtl.module @Bound(in %a: !firrtl.uint<8>) {
60+
%child_x = firrtl.instance child {doNotPrint} @ChildWithNonHardwareOutput(in x: !firrtl.uint<8>)
61+
firrtl.matchingconnect %child_x, %a : !firrtl.uint<8>
62+
}
63+
64+
// CHECK-LABEL: firrtl.module private @ChildWithNonHardwareOutput
65+
// CHECK-NOT: InlineAnnotation
66+
firrtl.module private @ChildWithNonHardwareOutput(in %x: !firrtl.uint<8>) attributes {annotations = [{class = "firrtl.transforms.DontTouchAnnotation"}]} {}
67+
}

test/firtool/input-only.fir

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
; RUN: firtool %s --inline-input-only-modules | FileCheck %s --check-prefix INLINE
2+
; RUN: firtool %s | FileCheck %s
3+
4+
5+
FIRRTL version 5.0.0
6+
circuit Top:
7+
; INLINE-NOT: module InputOnly
8+
; INLINE: `define ref_Top_probe input_probe
9+
; CHECK: module InputOnly
10+
; CHECK: `define ref_Top_probe input_only.input_probe
11+
module InputOnly:
12+
; Non-hardware ports must not be counted as output
13+
input input: UInt<1>
14+
output probe: Probe<UInt<1>>
15+
output x: Integer
16+
17+
define probe = probe(input)
18+
propassign x, Integer(1)
19+
20+
public module Top:
21+
input input: UInt<1>
22+
output probe: Probe<UInt<1>>
23+
output x: Integer
24+
25+
inst input_only of InputOnly
26+
connect input_only.input, input
27+
define probe = input_only.probe
28+
propassign x, input_only.x

0 commit comments

Comments
 (0)