Skip to content

Emit better code for linear mux trees #675

@seldridge

Description

@seldridge

The FIRRTL Dialect doesn't have a switch primitive and instead relies on mux trees to describe conditional assignment. Some of these mux trees are super regular and could be raised to more semantically meaningful Verilog/SystemVerilog constructs like case, casex, or casez.

Add the case variants to the SystemVerilog dialect and explore raising mux trees into these.

Example

The following FIRRTL code is describing a seven segment decoder which would look a lot better as a case:

circuit SevenSegmentDecoder :
  module SevenSegmentDecoder :
    input in : UInt<4>
    output out : UInt<7>

    node _T = eq(UInt<4>("h1"), in) 
    node _T_1 = mux(_T, UInt<6>("h6"), UInt<6>("h3f")) 
    node _T_2 = eq(UInt<4>("h2"), in) 
    node _T_3 = mux(_T_2, UInt<7>("h5b"), pad(_T_1, 7)) 
    node _T_4 = eq(UInt<4>("h3"), in) 
    node _T_5 = mux(_T_4, UInt<7>("h4f"), _T_3) 
    node _T_6 = eq(UInt<4>("h4"), in) 
    node _T_7 = mux(_T_6, UInt<7>("h66"), _T_5) 
    node _T_8 = eq(UInt<4>("h5"), in) 
    node _T_9 = mux(_T_8, UInt<7>("h6d"), _T_7) 
    node _T_10 = eq(UInt<4>("h6"), in) 
    node _T_11 = mux(_T_10, UInt<7>("h7d"), _T_9) 
    node _T_12 = eq(UInt<4>("h7"), in) 
    node _T_13 = mux(_T_12, UInt<7>("h7"), _T_11) 
    node _T_14 = eq(UInt<4>("h8"), in) 
    node _T_15 = mux(_T_14, UInt<7>("h7f"), _T_13) 
    node _T_16 = eq(UInt<4>("h9"), in) 
    node _T_17 = mux(_T_16, UInt<7>("h6f"), _T_15) 
    node _T_18 = eq(UInt<4>("ha"), in) 
    node _T_19 = mux(_T_18, UInt<7>("h77"), _T_17) 
    node _T_20 = eq(UInt<4>("hb"), in) 
    node _T_21 = mux(_T_20, UInt<7>("h7c"), _T_19) 
    node _T_22 = eq(UInt<4>("hc"), in) 
    node _T_23 = mux(_T_22, UInt<7>("h39"), _T_21) 
    node _T_24 = eq(UInt<4>("hd"), in) 
    node _T_25 = mux(_T_24, UInt<7>("h5e"), _T_23) 
    node _T_26 = eq(UInt<4>("he"), in) 
    node _T_27 = mux(_T_26, UInt<7>("h79"), _T_25) 
    node _T_28 = eq(UInt<4>("hf"), in) 
    out <= mux(_T_28, UInt<7>("h71"), _T_27)

Currently, CIRCT produces the following Verilog for this (firtool -lower-to-rtl -enable-lower-types -verilog SevenSegmentDecoder.fir):

module SevenSegmentDecoder(
  input  [3:0] in,
  output [6:0] out);

  assign out = &in ? 7'h71 : in == 4'hE ? 7'h79 : in == 4'hD ? 7'h5E : in == 4'hC ? 7'h39 : in == 4'hB ? 7'h7C : in == 4'hA ? 7'h77 : in == 4'h9 ? 7'h6F : in == 4'h8 ? 7'h7F : in == 4'h7 ? 7'h7 : in == 4'h6 ? 7'h7D : in == 4'h5 ? 7'h6D : in == 4'h4 ? 7'h66 : in == 4'h3 ? 7'h4F : in == 4'h2 ? 7'h5B : {1'h0, in == 4'h1 ? 6'h6 : 6'h3F};	// SevenSegmentDecoder.fir:2:3, :6:15, :7:{17,40}, :8:17, :9:{17,27}, :10:17, :11:{17,27}, :12:17, :13:{17,27}, :14:17, :15:{17,27}, :16:18, :17:{18,29}, :18:18, :19:18, :20:{18,21}, :21:{18,29}, :22:{18,21}, :23:{18,29}, :24:{18,21}, :25:{18,29}, :26:{18,21}, :27:{18,29}, :28:{18,21}, :29:18, :30:{18,21}, :31:{18,29}, :32:{18,21}, :33:{18,29}, :34:18, :35:{18,29}
endmodule

The Scala FIRRTL Compiler is more structurally similar to the input FIRRTL, but is still structural (firrtl -i SevenSegmentDecoder.fir):

module SevenSegmentDecoder(
  input  [3:0] in,
  output [6:0] out
);
  wire [5:0] _T_1 = 4'h1 == in ? 6'h6 : 6'h3f;
  wire [6:0] _T_3 = 4'h2 == in ? 7'h5b : {{1'd0}, _T_1};
  wire [6:0] _T_5 = 4'h3 == in ? 7'h4f : _T_3;
  wire [6:0] _T_7 = 4'h4 == in ? 7'h66 : _T_5;
  wire [6:0] _T_9 = 4'h5 == in ? 7'h6d : _T_7;
  wire [6:0] _T_11 = 4'h6 == in ? 7'h7d : _T_9;
  wire [6:0] _T_13 = 4'h7 == in ? 7'h7 : _T_11;
  wire [6:0] _T_15 = 4'h8 == in ? 7'h7f : _T_13;
  wire [6:0] _T_17 = 4'h9 == in ? 7'h6f : _T_15;
  wire [6:0] _T_19 = 4'ha == in ? 7'h77 : _T_17;
  wire [6:0] _T_21 = 4'hb == in ? 7'h7c : _T_19;
  wire [6:0] _T_23 = 4'hc == in ? 7'h39 : _T_21;
  wire [6:0] _T_25 = 4'hd == in ? 7'h5e : _T_23;
  wire [6:0] _T_27 = 4'he == in ? 7'h79 : _T_25;
  assign out = 4'hf == in ? 7'h71 : _T_27;
endmodule

Ideally, this would be represented as something like:

always_comb begin
  case(in)
    4'h0:    out = 7'h3F;
    4'h1:    out = 7'h06;
    4'h2:    out = 7'h5B;
    4'h3:    out = 7'h4F;
    4'h4:    out = 7'h66;
    4'h5:    out = 7'h6D;
    4'h6:    out = 7'h7D;
    4'h7:    out = 7'h07;
    4'h8:    out = 7'h7F;
    4'h9:    out = 7'h6F;
    4'hA:    out = 7'h77;
    4'hb:    out = 7'h7C;
    4'hC:    out = 7'h39;
    4'hd:    out = 7'h5E;
    4'hE:    out = 7'h79;
    4'hF:    out = 7'h71;
  endcase
end

This example came from using the MuxLookup utility in Chisel. See this Scastie snippet.

Metadata

Metadata

Assignees

No one assigned

    Labels

    FIRRTLInvolving the `firrtl` dialectVerilog/SystemVerilogInvolving a Verilog dialectenhancementNew feature or request

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions