Implicit return for when-elseWhen-otherwise statements
Type of issue: Feature Request
Is your feature request related to a problem? Please describe.
When writing register update sequences, I often find myself having to do the following:
when (cond_1) {
val val_1 = // ...
my_reg := val_1
} .elseWhen(cond_2) {
val val_2 = // ...
my_reg := val_2
} .otherwise {
val val_3 = // ...
my_reg := val_3
}
This is somewhat clunky as it means I have to keep stuffing an assignment at the end of each block.
As an alternative, I've seem some projects (BOOM?) do the following instead:
val val_1 = // ...
val val_2 = // ...
val val_3 = // ...
my_reg := Mux(cond_1, val_1,
Mux(cond_2, val_2, val_3))
which, while denser, is still somewhat odd and closer to the HW implementation than I would normal expect from Chisel. It also gets a bit messy as the all of the intermediate calculations for val_xxx are disconnected from their conditions and end up in a pile above the mux tree. Worse, if you have multiple nested conditions (i.e. imagine a second when-otherwise inside the when(cond1) block), it becomes legitimately challenging to figure out what the value will actually be as you have to reconstruct the control-flow tree in your head.
Describe the solution you'd like Scala supports implicit returns where the last statement inside a scope is returned. It would be great if this were applied to when-elseWhen-otherwise chains so that we could write the following:
my_reg :=
when (cond_1) {
val val_1 = // ...
val_1
} .elseWhen(cond_2) {
val val_2 = // ...
val_2
} .otherwise {
val val_3 = // ...
val_3
}
For example, a toy ALU could be written as:
alu_result :=
when (op === ALUOp.ADD) {
arg1 + arg2
} .elseWhen(op === ALUOp.SUB) {
arg1 - arg2
} .otherwise {
DontCare
}
In my eyes, this is both cleaner and more Scala-idiomatic.
I'm definitely not a Chisel (or Scala) expert, but I think this should be possible to implement (and statically type checkable) by making WhenContext generic on type T where T is the type of the return value of the block. Then, further contexts can be chained iff T1==T2.
One challenge here is what to do when the expression is not exhaustive (i.e. suppose we omit the otherwise case in the ALU example above). I would personally prefer if this were treated as an error rather than the assignee keeping its previous value or, worse, getting implicitly DontCare'd as it avoids introducing footguns. This does pose a problem for trying to use this with switch statements which don't currently support a default case but I'd prefer that be solved by simply adding a default case rather than making the construct do strange and dangerous things when you use it wrong.
Isn't this doable with MuxCase or Mux1H?
Isn't this doable with MuxCase or Mux1H?
Huh, I did overlook MuxCase, that's a good practical solution. I'll definitely switch to using that, thanks!
I do think it still makes more sense from a Scala perspective to have this at least supported by whens. It's unexpected that you'd be able to write:
val result = if (cond) {
val1
} else {
val 2
}
but not
val result = when (cond) {
val1
} .otherwise {
val2
}
Though, I don't know how much weight "making the Chisel API look like Scala" holds :)
I think what you really need here is the Decoder design pattern.
From my RTL experience, I prefer using when only in the control path.