chipsalliance / chipsalliance/chisel
[rfc] Case/If Else Statement Emission
- Dominant language
- Scala
- Stars
- 4.8k
- Forks
- 658
- Avg merge
- 18h 59m
- Merged PRs (30d)
- 14
Description
Currently, Verilog emission of `when`/`.elsewhen`/`.otherwise` or `switch`/`is` results in a sequence of nested `if`/`else` in the resulting Verilog. While synthesis tools may be perfectly fine with this structure, human users are not fine with this seemingly unintuitive behavior.
This proposal concerns a push to improve this generation by supporting emission of Verilog `case` statements. As a corollary, this proposal *may* naturally also cover generation of `else if` statements.
Roughly, there are two approaches to doing this: (1) a *heuristic* based approach and (2) a library based approach.
Consider the following example:
```scala
val outSwitch = IO(Output(Bool()))
outSwitch := {
val tmp = RegInit(false.B)
switch (in) {
is(0.U) { tmp := false.B }
is(1.U) { tmp := false.B }
is(2.U) { tmp := false.B }
is(3.U) { tmp := true.B }
}
tmp
}
```
Producing the following Low FIRRTL:
```
reg _T : UInt<1>, clock with :
reset => (UInt<1>("h0"), _T) @[ 21:22]
node _T_1 = eq(UInt<2>("h0"), in) @[Conditional.scala 37:30]
node _T_2 = eq(UInt<2>("h1"), in) @[Conditional.scala 37:30]
node _T_3 = eq(UInt<2>("h2"), in) @[Conditional.scala 37:30]
node _T_4 = eq(UInt<2>("h3"), in) @[Conditional.scala 37:30]
node _GEN_0 = or(_T_4, _T) @[Conditional.scala 39:67]
node _GEN_1 = mux(_T_3, UInt<1>("h0"), _GEN_0) @[Conditional.scala 39:67]
node _GEN_2 = mux(_T_2, UInt<1>("h0"), _GEN_1) @[Conditional.scala 39:67]
node _GEN_3 = mux(_T_1, UInt<1>("h0"), _GEN_2) @[Conditional.scala 40:58]
```
## Heuristics
The above example has the following properties:
1. The failure condition of each `mux` is a reference to another `mux` (except in the default)
2. The condition for each `mux` is an `eq` involving a constant and a reference (`foo`)
3. Each reference (`foo`) is the same
4. Each constant is mutually exclusive with all other constants
Observing these four properties (which are simple to check), it seems natural that these muxes can be grouped into one `case` statement.
A concrete way to do this would be to:
1. Create an annotation storing this information
2. Modify the `VerilogEmitter` to use this annotation during emission
Alternatively, this could be done with a `CombineMuxes` transform that replaces references to the failure condition with the actual `Mux`. It's then relatively trivial for the emitter to verify that these conditions continue to hold when emitting.
## Library
In this approach, there is no heuristic and we rely on a new Chisel library (backend by annotations) to guide the emission.
This is potentially more complicated as FIRRTL has no underlying primitive to represent case/switch, only `when`. Here, the above guarantees would have to be checked by an explicit transform or by the emitter to guarantee that no modifications have happened that would result in an improper case.
## QOR
The QOR of the emitted Verilog with respect to FPGA/ASIC toolchains with this proposed modification is unknown at this time.
### Example
Consider the following Chisel code that implements an AND gate using both `switch` and `when`:
```scala
import chisel3._
import chisel3.util.{switch, is, SwitchContext}
import chisel3.stage.{ChiselGeneratorAnnotation, ChiselStage}
class Foo extends MultiIOModule {
val in = IO(Input(UInt(2.W)))
val outSwitch = IO(Output(Bool()))
val outWhen = IO(Output(Bool()))
outSwitch := {
val tmp = RegInit(false.B)
switch (in) {
is(0.U) { tmp := false.B }
is(1.U) { tmp := false.B }
is(2.U) { tmp := false.B }
is(3.U) { tmp := true.B }
}
tmp
}
outWhen := {
val tmp = RegInit(false.B)
when(in === 0.U) { tmp := false.B }
.elsewhen(in === 1.U) { tmp := false.B }
.elsewhen(in === 2.U) { tmp := false.B }
.otherwise { tmp := true.B }
tmp
}
}
(new ChiselStage)
.execute(Array("-X", "verilog"), Seq(ChiselGeneratorAnnotation(() => new Foo)))
```
This produces the following FIRRTL:
```
;buildInfoPackage: chisel3, version: 3.2.0-RC2, scalaVersion: 2.12.10, sbtVersion: 1.2.7
circuit readiwiwFoo :
module readiwiwFoo :
input clock : Clock
input reset : UInt<1>
input in : UInt<1>
output outSwitch : UInt<1>
output outWhen : UInt<1>
reg _T : UInt<1>, clock with : (reset => (reset, UInt<1>("h00"))) @[ 21:22]
node _T_1 = eq(UInt<1>("h00"), in) @[Conditional.scala 37:30]
when _T_1 : @[Conditional.scala 40:58]
_T <= UInt<1>("h00") @[ 23:21]
skip @[Conditional.scala 40:58]
else : @[Conditional.scala 39:67]
node _T_2 = eq(UInt<1>("h01"), in) @[Conditional.scala 37:30]
when _T_2 : @[Conditional.scala 39:67]
_T <= UInt<1>("h00") @[ 24:21]
skip @[Conditional.scala 39:67]
else : @[Conditional.scala 39:67]
node _T_3 = eq(UInt<2>("h02"), in) @[Conditional.scala 37:30]
when _T_3 : @[Conditional.scala 39:67]
_T <= UInt<1>("h00") @[ 25:21]
skip @[Conditional.scala 39:67]
else : @[Conditional.scala 39:67]
node _T_4 = eq(UInt<2>("h03"), in) @[Conditional.scala 37:30]
when _T_4 : @[Conditional.scala 39:67]
_T <= UInt<1>("h01") @[ 26:21]
skip @[Conditional.scala 39:67]
outSwitch <= _T @[ 20:13]
reg _T_5 : UInt<1>, clock with : (reset => (reset, UInt<1>("h00"))) @[ 32:22]
node _T_6 = eq(in, UInt<1>("h00")) @[ 33:13]
when _T_6 : @[ 33:29]
_T_5 <= UInt<1>("h00") @[ 33:35]
skip @[ 33:29]
else : @[ 34:29]
node _T_7 = eq(in, UInt<1>("h01")) @[ 34:20]
when _T_7 : @[ 34:29]
_T_5 <= UInt<1>("h00") @[ 34:35]
skip @[ 34:29]
else : @[ 35:29]
node _T_8 = eq(in, UInt<2>("h02")) @[ 35:20]
when _T_8 : @[ 35:29]
_T_5 <= UInt<1>("h00") @[ 35:35]
skip @[ 35:29]
else : @[ 36:29]
_T_5 <= UInt<1>("h01") @[ 36:35]
skip @[ 36:29]
outWhen <= _T_5 @[ 31:11]
```
And the following Verilog:
```verilog
module readiwiwFoo(
input clock,
input reset,
input in,
output outSwitch,
output outWhen
);
reg _T; // @[ 21:22]
reg [31:0] _RAND_0;
wire _T_1; // @[Conditional.scala 37:30]
wire [1:0] _GEN_7; // @[Conditional.scala 37:30]
wire _T_3; // @[Conditional.scala 37:30]
wire _T_4; // @[Conditional.scala 37:30]
wire _GEN_0; // @[Conditional.scala 39:67]
reg _T_5; // @[ 32:22]
reg [31:0] _RAND_1;
wire _T_6; // @[ 33:13]
wire _T_8; // @[ 35:20]
assign _T_1 = 1'h0 == in; // @[Conditional.scala 37:30]
assign _GEN_7 = {{1'd0}, in}; // @[Conditional.scala 37:30]
assign _T_3 = 2'h2 == _GEN_7; // @[Conditional.scala 37:30]
assign _T_4 = 2'h3 == _GEN_7; // @[Conditional.scala 37:30]
assign _GEN_0 = _T_4 | _T; // @[Conditional.scala 39:67]
assign _T_6 = in == 1'h0; // @[ 33:13]
assign _T_8 = _GEN_7 == 2'h2; // @[ 35:20]
assign outSwitch = _T; // @[ 20:13]
assign outWhen = _T_5; // @[ 31:11]
always @(posedge clock) begin
if (reset) begin
_T <= 1'h0;
end else begin
if (_T_1) begin
_T <= 1'h0;
end else begin
if (in) begin
_T <= 1'h0;
end else begin
if (_T_3) begin
_T <= 1'h0;
end else begin
_T <= _GEN_0;
end
end
end
end
if (reset) begin
_T_5 <= 1'h0;
end else begin
if (_T_6) begin
_T_5 <= 1'h0;
end else begin
if (in) begin
_T_5 <= 1'h0;
end else begin
if (_T_8) begin
_T_5 <= 1'h0;
end else begin
_T_5 <= 1'h1;
end
end
end
end
end
endmodule
```
**Type of issue**: feature request
**Impact**: API modification
**Development Phase**: request | proposal
**Other information**
**What is the use case for changing the behavior?**
Better Verilog generation.
Contributor guide
Research direction
Start with the VerilogEmitter and the FIRRTL representation of when/mux chains described in the issue. Compare the proposed annotation-based and CombineMuxes approaches, including how their heuristics preserve semantics. Done means supported patterns emit Verilog case or else-if structures instead of nested if/else while preserving behavior; QOR remains to be evaluated.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- scala
- Domain
- compilers
- Issue type
- Feature
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Stale
- Clarity
- Mostly clear
- Newbie friendliness
- 25/100