Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[FIRRTL] no back-prop for width of mux selectors, support narrower #6917

Merged
merged 5 commits into from
Apr 17, 2024

Conversation

dtzSiFive
Copy link
Contributor

@dtzSiFive dtzSiFive commented Apr 13, 2024

Support parsing mux with selector of zero-width, per spec.

Fix use as mux selector "back-propagating" (acts as-if connected to from a 1-bit source).

Canonicalize narrower selectors to full width for lowering and analysis simplicity.

Fixes #5444.

@seldridge
Copy link
Member

This would be good to spot check with internal designs see if anything breaks and if there are unexpected Verilog diffs. (The other direction is trying to ratchet this up on the Chisel side by requiring Bool as an input to the mux select line as you suggested. 😉) I'm generally curious how many places this is relied on.

@dtzSiFive
Copy link
Contributor Author

Ran on few internal designs and so far the verilog is identical 👍 .

@@ -1596,12 +1596,12 @@ LogicalResult InferenceMapping::mapOperation(Operation *op) {
})
.Case<MuxPrimOp, Mux2CellIntrinsicOp>([&](auto op) {
auto *sel = getExpr(op.getSel());
constrainTypes(sel, solver.known(1));
constrainTypes(solver.known(1), sel, /*imposeUpperBounds=*/true);
Copy link
Contributor Author

Choose a reason for hiding this comment

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

The constraint change here goes from adding sel >= 1 to sel <= 1 which isn't used for solving but is checked for correctness.

Copy link
Member

@seldridge seldridge left a comment

Choose a reason for hiding this comment

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

This seems reasonable. One question/concern about if the canonicalization is mandatory for InferWidths.

Comment on lines +566 to +568
// CHECK-NEXT: firrtl.strictconnect %out5, %val2
%16 = firrtl.mux (%val0, %val1, %val2) : (!firrtl.uint<0>, !firrtl.uint<1>, !firrtl.uint<1>) -> !firrtl.uint<1>
firrtl.strictconnect %out5, %16 : !firrtl.uint<1>
Copy link
Member

Choose a reason for hiding this comment

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

If this canonicalization doesn't happen, will this error in InferWidths? I.e., is this illegal FIRRTL?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is now legal FIRRTL (ODS change), this helps with things like LowerToHW not knowing how to deal with this sort of selector and generally needing better handling for zero-width things, and to allow participating in/composing with other canonicalizations (e.g., existing canonicalization for constant-false selector, which is what is tested here).

InferWidths runs before canonicalization (so for sure this won't happen until after InferWidths), but it's not illegal if this doesn't run at any point. It just helps clean things up and accommodate brittle passes.

include/circt/Dialect/FIRRTL/FIRRTLTypes.td Outdated Show resolved Hide resolved
Comment on lines +591 to +598
// mux4(cond : u0/u1, a, b) -> mux4(pad(cond -> u2), a, b)
def Mux4PadSel : Pat<
(Mux4CellIntrinsicOp:$old $cond, $a, $b, $c, $d),
(MoveNameHint $old, (Mux4CellIntrinsicOp
(PadPrimOp $cond,
(NativeCodeCall<"$_builder.getI32IntegerAttr(2)">)),
$a, $b, $c, $d)),
[(IntTypeWidthLTX<2> $cond)]>;
Copy link
Member

Choose a reason for hiding this comment

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

Assumedly, the asymmetry of the mux4 getting canonicalized to a pad will be immediately cleaned up by a canonicalization that converts pad(_: u0, x) -> 0: u<x>?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

That's the idea but such a canonicalization doesn't exist presently. I've started working on adding this dialect-wide because I'm tired of playing (or seeing others play) whack-a-mole with zero-width anything (WIP/TBD) but yeah that's the idea. And the 1bit is still a pad -- which maybe is something a mux4 could handle and become a mux2 or something (?). Anyway presently Mux4 is missing all of the mux2 folding logic, FWIW.

FWIW, honestly, re:asymmetry -- replacing it with a pad uniformly is what I reached for first -- one thing at a time, but since the pattern doesn't exist just inserted the constant for the 2-mux directly.

I don't think this is a problem (or at least okay to handle separately), the extension used to (/currently, without this PR) implicitly in the connect which is canonicalized to the same pad anyway, consider:

firrtl.circuit "MuxIntrinsics" {
  firrtl.module @MuxIntrinsics(in %sel_0w: !firrtl.uint<0>, in %sel_1w: !firrtl.uint<1>, in %high: !firrtl.uint<1>, in %low: !firrtl.uint<1>, out %out1: !firrtl.uint, out %out2: !firrtl.uint) {
    %c3_ui4 = firrtl.constant 3 : !firrtl.uint<4>
    %c3_ui3 = firrtl.constant 3 : !firrtl.uint<3>
    %c2_ui2 = firrtl.constant 2 : !firrtl.uint<2>
    %c1_ui1 = firrtl.constant 1 : !firrtl.uint<1>
    %c1_ui2 = firrtl.constant 1 : !firrtl.uint<2>
    %c0_ui1 = firrtl.constant 0 : !firrtl.uint<1>
    %c1 = firrtl.constant 0: !firrtl.uint
    %sel = firrtl.wire : !firrtl.uint
    firrtl.connect %sel, %sel_0w : !firrtl.uint, !firrtl.uint<0>
    %0 = firrtl.int.mux2cell(%sel, %c0_ui1, %c1) : (!firrtl.uint, !firrtl.uint<1>, !firrtl.uint) -> !firrtl.uint
    firrtl.connect %out1, %0: !firrtl.uint, !firrtl.uint
    %sel2 = firrtl.wire : !firrtl.uint
    firrtl.connect %sel2, %sel_1w : !firrtl.uint, !firrtl.uint<1>
    %1 = firrtl.int.mux4cell(%sel2, %c1_ui1, %c2_ui2, %c3_ui3, %c1) : (!firrtl.uint, !firrtl.uint<1>, !firrtl.uint<2>, !firrtl.uint<3>, !firrtl.uint) -> !firrtl.uint
    firrtl.connect %out2, %1: !firrtl.uint, !firrtl.uint
  }
}

Which if you feed to today's firtool and look at the IR after canonicalize runs, you get:

firrtl.module @MuxIntrinsics(in %sel_0w: !firrtl.uint<0>, in %sel_1w: !firrtl.uint<1>, in %high: !firrtl.uint<1>, in %low: !firrtl.uint<1>, out %out1: !firrtl.uint<1>, out %out2: !firrtl.uint<3>) {
  %c3_ui3 = firrtl.constant 3 : !firrtl.uint<3>
  %c2_ui2 = firrtl.constant 2 : !firrtl.uint<2>
  %c1_ui1 = firrtl.constant 1 : !firrtl.uint<1>
  %c0_ui1 = firrtl.constant 0 : !firrtl.uint<1>
  %sel = firrtl.wire : !firrtl.uint<1>
  %0 = firrtl.pad %sel_0w, 1 : (!firrtl.uint<0>) -> !firrtl.uint<1>
  firrtl.strictconnect %sel, %0 : !firrtl.uint<1>
  firrtl.strictconnect %out1, %c0_ui1 : !firrtl.uint<1>
  %sel2 = firrtl.wire : !firrtl.uint<2>
  %1 = firrtl.pad %sel_1w, 2 : (!firrtl.uint<1>) -> !firrtl.uint<2>
  firrtl.strictconnect %sel2, %1 : !firrtl.uint<2>
  %2 = firrtl.int.mux4cell(%sel2, %c1_ui1, %c2_ui2, %c3_ui3, %c0_ui1) : (!firrtl.uint<2>, !firrtl.uint<1>, !firrtl.uint<2>, !firrtl.uint<3>, !firrtl.uint<1>) -> !firrtl.uint<3>
  firrtl.strictconnect %out2, %2 : !firrtl.uint<3>
}

That is to say this is at worst no worse than what we have today.

Copy link
Member

Choose a reason for hiding this comment

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

Sounds good. Yeah, I was thinking of using pad for the Mux and Mux2 canonicalizations, as well. However, it seems reasonable to just squash those when you see them.

Thanks!

Co-authored-by: Schuyler Eldridge <schuyler.eldridge@gmail.com>
@dtzSiFive
Copy link
Contributor Author

TYVM for taking a look and the great feedback!

@dtzSiFive
Copy link
Contributor Author

Spec change this is supporting, FWIW: chipsalliance/firrtl-spec#122 .

@dtzSiFive dtzSiFive merged commit ea77394 into llvm:main Apr 17, 2024
4 checks passed
@dtzSiFive dtzSiFive deleted the feature/mux-backprop branch April 17, 2024 12:43
cepheus69 pushed a commit to cepheus69/circt that referenced this pull request Apr 22, 2024
…lvm#6917)

* Allow mux selectors to be zero-width or 1-bit (for mux4).

This is legal per FIRRTL spec.

* InferWidths: mux no back-prop.

Fixes llvm#5444

* canonicalizers for small mux selectors

Co-authored-by: Schuyler Eldridge <schuyler.eldridge@gmail.com>
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.

[FIRRTL][InferWidths] Back-prop for mux selector operand; different than subaccess
2 participants