Skip to content

[HW] Add pass to parameterize constant ports on private modules.#9266

Merged
uenoku merged 2 commits into
llvm:mainfrom
uenoku:dev/hidetou/constant
Dec 2, 2025
Merged

[HW] Add pass to parameterize constant ports on private modules.#9266
uenoku merged 2 commits into
llvm:mainfrom
uenoku:dev/hidetou/constant

Conversation

@uenoku
Copy link
Copy Markdown
Member

@uenoku uenoku commented Nov 26, 2025

This pass converts input ports on private modules into parameters when all instances pass constant values (or parameter values) to those ports.

By converting constant ports to parameters, synthesis pipelines can recognize these values as compile-time constants through local analysis alone, without requiring inter-module analysis. This enables more precise timing information and better optimization of each instance independently, since the constant values are immediately visible at the module interface.

This pass converts input ports on private modules into parameters when all
instances pass constant values (or parameter values) to those ports.

By converting constant ports to parameters, synthesis pipelines can
recognize these values as compile-time constants through local analysis
alone, without requiring inter-module analysis. This enables more precise
timing information and better optimization of each instance independently,
since the constant values are immediately visible at the module interface.
@uenoku uenoku requested a review from darthscsi as a code owner November 26, 2025 03:11
Copy link
Copy Markdown
Contributor

@cowardsa cowardsa left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM - nice change to simplify the analyses!!

I didn't see a test for if a port is used as both constant and variable, which might be nice to check for as you wouldn't want that to be parameterized?

Comment thread lib/Dialect/HW/Transforms/HWParameterizeConstantPorts.cpp Outdated
Comment on lines +156 to +158
// Build new port names for instances (excluding removed ports)
DenseSet<unsigned> portsToRemoveSet(portsToParameterize.begin(),
portsToParameterize.end());
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Non-Blocking - an extension could check whether all the ports are unique and fuse ports that are always identical - but perhaps this is handled by a different pass already

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I agree such pass is very beneficial. #4638 is similar issue.

Comment thread lib/Dialect/HW/Transforms/HWParameterizeConstantPorts.cpp
%c1_i4 = hw.constant 1 : i4
// CHECK: %inst1.out = hw.instance "inst1" @MultipleConstantPorts<id: i8 = 1, mode: i4 = 1>
// CHECK-SAME: (data: %data: i32) -> (out: i32)
%inst1.out = hw.instance "inst1" @MultipleConstantPorts(id: %c1_i8: i8, mode: %c1_i4: i4, data: %data: i32) -> (out: i32)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To incorporate a test that modules with ports that have both constant and variable instances - could make the mode port variable in this second instance?

Copy link
Copy Markdown
Member Author

@uenoku uenoku Dec 2, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great point! I changed mode to a variable (and added another port mode2 to test multiple parameters) 👍

Copy link
Copy Markdown
Contributor

@dtzSiFive dtzSiFive left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fantastic work @uenoku !! 💯 🥳
(Thank you!)

Pass is very clear and clean, tests LGTM as well. Didn't fall over on various special cases, yet pass isn't cluttered with a bunch of one-offs. Awesome.

Left some comments, least "nit" was fixing bottom-up vs top-down in a comment 😉 .

module.setParametersAttr(builder.getArrayAttr(newParameters));

// Remove the parameterized ports from the module signature
module.modifyPorts({}, {}, portsToParameterize, {});
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this the only port mutation method we have? Not for this PR, but it uses a in-theory "deprecated" method modifyModulePorts as its implementation, so I'm surprised we aren't discouraging its use?

Not for this PR, looks like? 👍

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, this is not great. I think there is no alternative method for port mutations now...

Comment thread lib/Dialect/HW/Transforms/HWParameterizeConstantPorts.cpp Outdated
Comment thread lib/Dialect/HW/Transforms/HWParameterizeConstantPorts.cpp
static Attribute getAttributeValue(Value value) {
if (auto constOp = value.getDefiningOp<hw::ConstantOp>())
return IntegerAttr::get(value.getType(), constOp.getValue());
if (auto paramOp = value.getDefiningOp<hw::ParamValueOp>())
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hyper-nit and honestly it's great as-is!

Per your judgement (!), but I'm looking at these methods (getAttributeValue and getConstantOrParamValue) and wondering if there's a slight organizational change that better captures / ensures that they both consider the same cases?

I'm less worried about the redundant "work" specifically but tiny possible clarity / maintenance improvement. And submitting the idea for your consideration 😉 .

As-is, the number of cases is low (!) and unlikely to change (especially with any frequency), and the methods as written are clear and concise 👍 . So I'm happy with them as-is for sure!

WDYT of grabbing the defining operation (well, its single result as a Value anyway) AND the value attribute in one go? Would save the llvm_unreachable which is in part where this thinking came from: What ensures that case is unreachable? --> The other method only covers those same cases too.

Would be neat if ConstantLike meant could easily grab the Attribute it would fold to...

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great point! I agree ConstantLike trait provides some interface method :) I unified two methods to getAttributeAndDefiningOp (actually it was very straightforward) so I think this is ok now. Thank you for the suggetion!


// Delete the constant or param.value op if it's only used by this
// instance.
if (constValue.hasOneUse()) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Appreciate keeping the IR tidy. If this pass is ever hooked up to something that's more of the result of an analysis this might not be practical (or safe) to clean up this way naively but that's not what this pass is about! 👍

@uenoku uenoku force-pushed the dev/hidetou/constant branch from 0e01cda to 13bf08d Compare December 2, 2025 03:18
@uenoku uenoku merged commit a851652 into llvm:main Dec 2, 2025
7 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants