XLS should hoist operations out of `select`s when clearly profitable (i.e. conditional arrives sufficiently early)
- Dominant language
- C++
- Stars
- 1.9k
- Forks
- 283
- Avg merge
- 2d 10h
- Merged PRs (30d)
- 135
Description
Consider the following fragment of code [pdk=asap7]
```
pub fn main(a: u8, b: u8, c: u8, d: u8) -> u8 {
if (d == u8:4) {
a << b
} else {
a << c
}
}
```
Optimized IR
```
top fn __user_module__main(a: bits[8], b: bits[8], c: bits[8], d: bits[8]) -> bits[8] {
literal.5: bits[8] = literal(value=4, id=5, pos=[(0,2,14)])
eq.6: bits[1] = eq(d, literal.5, id=6, pos=[(0,2,8)])
shll.8: bits[8] = shll(a, c, id=8, pos=[(0,5,6)])
shll.7: bits[8] = shll(a, b, id=7, pos=[(0,3,6)])
ret sel.9: bits[8] = sel(eq.6, cases=[shll.8, shll.7], id=9, pos=[(0,2,2)])
}
```
Verilog
```verilog
module user_module(
input wire clk,
input wire [7:0] a,
input wire [7:0] b,
input wire [7:0] c,
input wire [7:0] d,
output wire [7:0] out
);
// ===== Pipe stage 0:
// Registers for pipe stage 0:
reg [7:0] p0_a;
reg [7:0] p0_b;
reg [7:0] p0_c;
reg [7:0] p0_d;
always @ (posedge clk) begin
p0_a <= a;
p0_b <= b;
p0_c <= c;
p0_d <= d;
end
// ===== Pipe stage 1:
wire p1_eq_24_comb;
wire [7:0] p1_shll_25_comb;
wire [7:0] p1_shll_26_comb;
assign p1_eq_24_comb = p0_d == 8'h04;
assign p1_shll_25_comb = p0_c >= 8'h08 ? 8'h00 : p0_a << p0_c;
assign p1_shll_26_comb = p0_b >= 8'h08 ? 8'h00 : p0_a << p0_b;
// Registers for pipe stage 1:
reg p1_eq_24;
reg [7:0] p1_shll_25;
reg [7:0] p1_shll_26;
always @ (posedge clk) begin
p1_eq_24 <= p1_eq_24_comb;
p1_shll_25 <= p1_shll_25_comb;
p1_shll_26 <= p1_shll_26_comb;
end
// ===== Pipe stage 2:
wire [7:0] p2_sel_33_comb;
assign p2_sel_33_comb = p1_eq_24 ? p1_shll_26 : p1_shll_25;
// Registers for pipe stage 2:
reg [7:0] p2_sel_33;
always @ (posedge clk) begin
p2_sel_33 <= p2_sel_33_comb;
end
assign out = p2_sel_33;
endmodule
```
XLS does basically nothing to this input.
The stats for this (on asap7) are
value|count
---|---
num_wires| 375.0000
num_wire_bits| 466.0000
num_pub_wires| 16.0000
num_pub_wire_bits| 107.0000
num_memories| 0.0000
num_memory_bits| 0.0000
num_processes| 0.0000
num_cells| 198.0000
area| 24.1299
A small change to the input has a large change to the output however. If we change the xls to
```
pub fn main(a: u8, b: u8, c: u8, d: u8) -> u8 {
a << if (d == u8:4) {
b
} else {
c
}
}
```
Optimized IR
```
top fn __user_module__main(a: bits[8], b: bits[8], c: bits[8], d: bits[8]) -> bits[8] {
literal.5: bits[8] = literal(value=4, id=5, pos=[(0,2,19)])
eq.6: bits[1] = eq(d, literal.5, id=6, pos=[(0,2,13)])
sel.7: bits[8] = sel(eq.6, cases=[c, b], id=7, pos=[(0,2,7)])
ret shll.8: bits[8] = shll(a, sel.7, id=8, pos=[(0,2,4)])
}
```
Verilog
```verilog
module user_module(
input wire clk,
input wire [7:0] a,
input wire [7:0] b,
input wire [7:0] c,
input wire [7:0] d,
output wire [7:0] out
);
// ===== Pipe stage 0:
// Registers for pipe stage 0:
reg [7:0] p0_a;
reg [7:0] p0_b;
reg [7:0] p0_c;
reg [7:0] p0_d;
always @ (posedge clk) begin
p0_a <= a;
p0_b <= b;
p0_c <= c;
p0_d <= d;
end
// ===== Pipe stage 1:
wire [7:0] p1_sel_23_comb;
assign p1_sel_23_comb = p0_d == 8'h04 ? p0_b : p0_c;
// Registers for pipe stage 1:
reg [7:0] p1_a;
reg [7:0] p1_sel_23;
always @ (posedge clk) begin
p1_a <= p0_a;
p1_sel_23 <= p1_sel_23_comb;
end
// ===== Pipe stage 2:
wire [7:0] p2_shll_28_comb;
assign p2_shll_28_comb = p1_sel_23 >= 8'h08 ? 8'h00 : p1_a << p1_sel_23;
// Registers for pipe stage 2:
reg [7:0] p2_shll_28;
always @ (posedge clk) begin
p2_shll_28 <= p2_shll_28_comb;
end
assign out = p2_shll_28;
endmodule
```
The stats for this (on asap7) are
value | count
---|---
num_wires| 285.0000
num_wire_bits |376.0000
num_pub_wires |14.0000
num_pub_wire_bits| 105.0000
num_memories |0.0000
num_memory_bits| 0.0000
num_processes| 0.0000
num_cells |159.0000
area| 20.7036
This is a 15% improvement in area and 20% improvement in cell count.
XLS should recognize cases like this and lift common operations out of selects(/sink selects) where possible.
Contributor guide
Assessment
This issue has not been assessed yet.