四畳半テクノポリス

コロナのストレスで気が狂い、D進した院生

ChiselのBitpadとListLookupがまるでわからない?

命令デコーダって難しい

 CPUをVerilogで生成していたところ、命令デコーダの生成で作ったIf文にラッチができてしまいました。

 オペランドがそのままCase文でマルチプレクサなどの制御に使える単純なアーキテクチャであれば気にする必要はありませんが、RISC-Vは命令形式によって利用するビットフィールドが異なるため、複数の条件式を使ったIf文を書いて命令を特定し、その中で制御信号を設定したくなってしまいました。冷静に考えるとアホみたいな書き方です。また、「デジタル回路設計とコンピュータアーキテクチャ」を参照してもそのような書き方はされてません。

なんでこんな書き方をしてしまったかというと、「RSIC-VとChiselで学ぶ初めてのCPU自作」のListLookupが記憶に残っていたからだと思います。この本ではBitpadとListLookupという機能を使って命令デコーダを作っています。そこで実際にBitpadとListLookupで、どのような回路が生成されるか実験してみました。

Chiselコード

とりあえずBitpadとListLookアップを使った回路です

package lookup
import chisel3._
import chisel3.util._

class Top extends Module{
    val io = IO(new Bundle{
        val exit = Output(Bool())
        val outA = Output(UInt(4.W))
        val outB = Output(UInt(4.W))

    })

    val reg = RegInit(0.U(4.W))
    reg := reg + 1.U(4.W)

    val A = BitPat("b????0011")
    val B = BitPat("b????0111")
    val C = BitPat("b????1011")
    val D = BitPat("b????1111")
    val E = BitPat("b00??0000")
    val F = BitPat("b10??0000")
    val G = BitPat("b01??0000")
    val H = BitPat("b11??0000")
    
    val csignals = ListLookup(reg, List(1.U(4.W),1.U(4.W)),
        Array(
            A -> List(0.U(4.W),14.U(4.W)),
            B -> List(2.U(4.W),13.U(4.W)),
            C -> List(4.U(4.W),12.U(4.W)),
            D -> List(6.U(4.W),11.U(4.W)),
            E -> List(8.U(4.W),10.U(4.W)),
            F -> List(10.U(4.W),9.U(4.W)),
            G -> List(12.U(4.W),8.U(4.W)),
            H -> List(14.U(4.W),7.U(4.W))
        )
    )


    when(reg === 15.U(4.W)){
        io.exit := true.B
    }.otherwise{
        io.exit := false.B
    }

    val tmpA::tmpB::Nil = csignals 
    io.outA := tmpA
    io.outB := tmpB

}


object Elaborate extends App {
  chisel3.Driver.execute(args, () => new Top())
}

この回路からVerilogを生成すると次の回路のようになりました。

`ifdef RANDOMIZE_GARBAGE_ASSIGN
`define RANDOMIZE
`endif
`ifdef RANDOMIZE_INVALID_ASSIGN
`define RANDOMIZE
`endif
`ifdef RANDOMIZE_REG_INIT
`define RANDOMIZE
`endif
`ifdef RANDOMIZE_MEM_INIT
`define RANDOMIZE
`endif

module Top(
  input        clock,
  input        reset,
  output       io_exit,
  output [3:0] io_outA,
  output [3:0] io_outB
);
  reg [3:0] reg$;
  reg [31:0] _RAND_0;
  wire [4:0] _T_8;
  wire [3:0] _T_9;
  wire  _T_31;
  wire  _T_35;
  wire  _T_39;
  wire  _T_43;
  wire [7:0] _GEN_1;
  wire [7:0] _T_46;
  wire  _T_47;
  wire  _T_51;
  wire  _T_55;
  wire  _T_59;
  wire [3:0] _T_60;
  wire [3:0] _T_61;
  wire [3:0] _T_62;
  wire [3:0] _T_63;
  wire [3:0] _T_64;
  wire [3:0] _T_65;
  wire [3:0] _T_66;
  wire [3:0] csignals_0;
  wire [3:0] _T_67;
  wire [3:0] _T_68;
  wire [3:0] _T_69;
  wire [3:0] _T_70;
  wire [3:0] _T_71;
  wire [3:0] _T_72;
  wire [3:0] _T_73;
  wire [3:0] csignals_1;
  wire  _T_75;
  assign _T_8 = reg$ + 4'h1;
  assign _T_9 = _T_8[3:0];
  assign _T_31 = 4'h3 == reg$;
  assign _T_35 = 4'h7 == reg$;
  assign _T_39 = 4'hb == reg$;
  assign _T_43 = 4'hf == reg$;
  assign _GEN_1 = {{4'd0}, reg$};
  assign _T_46 = _GEN_1 & 8'hcf;
  assign _T_47 = 8'h0 == _T_46;
  assign _T_51 = 8'h80 == _T_46;
  assign _T_55 = 8'h40 == _T_46;
  assign _T_59 = 8'hc0 == _T_46;
  assign _T_60 = _T_59 ? 4'he : 4'h1;
  assign _T_61 = _T_55 ? 4'hc : _T_60;
  assign _T_62 = _T_51 ? 4'ha : _T_61;
  assign _T_63 = _T_47 ? 4'h8 : _T_62;
  assign _T_64 = _T_43 ? 4'h6 : _T_63;
  assign _T_65 = _T_39 ? 4'h4 : _T_64;
  assign _T_66 = _T_35 ? 4'h2 : _T_65;
  assign csignals_0 = _T_31 ? 4'h0 : _T_66;
  assign _T_67 = _T_59 ? 4'h7 : 4'h1;
  assign _T_68 = _T_55 ? 4'h8 : _T_67;
  assign _T_69 = _T_51 ? 4'h9 : _T_68;
  assign _T_70 = _T_47 ? 4'ha : _T_69;
  assign _T_71 = _T_43 ? 4'hb : _T_70;
  assign _T_72 = _T_39 ? 4'hc : _T_71;
  assign _T_73 = _T_35 ? 4'hd : _T_72;
  assign csignals_1 = _T_31 ? 4'he : _T_73;
  assign _T_75 = reg$ == 4'hf;
  assign io_exit = _T_75;
  assign io_outA = csignals_0;
  assign io_outB = csignals_1;
`ifdef RANDOMIZE
  integer initvar;
  initial begin
    `ifndef verilator
      #0.002 begin end
    `endif
  `ifdef RANDOMIZE_REG_INIT
  _RAND_0 = {1{$random}};
  reg$ = _RAND_0[3:0];
  `endif // RANDOMIZE_REG_INIT
  end
`endif // RANDOMIZE
  always @(posedge clock) begin
    if (reset) begin
      reg$ <= 4'h0;
    end else begin
      reg$ <= _T_9;
    end
  end
endmodule

どうやら出力ごと、にいい感じに組み合わせ回路を合成してくれるようです。カルノー図で解くような最適化をしているのかな?

不可解な点

ただ、Bitpadが矛盾していてもListLookupはエラーを吐かずに回路を生成してしまいました。次のコードですと0b00000011が与えられた場合AとEの両方に該当してしまうと思うんですが、回路は生成されます。ちゃんとエラー出してほしいお

    val A = BitPat("b????0011")
    val B = BitPat("b????0111")
    val C = BitPat("b????1011")
    val D = BitPat("b????1111")
    val E = BitPat("b00??????")
    val F = BitPat("b10??0000")
    val G = BitPat("b01??0000")
    val H = BitPat("b11??0000")

 全部同じにしてみた

試しにBitpadの値を次のように全部同じにしてみました。

    val A = BitPat("b????1111")
    val B = BitPat("b????1111")
    val C = BitPat("b????1111")
    val D = BitPat("b????1111")
    val E = BitPat("b????1111")
    val F = BitPat("b????1111")
    val G = BitPat("b????1111")
    val H = BitPat("b????1111")
    

すると生成されるコードは次のようになりました。

odule Top(
  input        clock,
  input        reset,
  output       io_exit,
  output [3:0] io_outA,
  output [3:0] io_outB
);
  reg [3:0] reg$;
  reg [31:0] _RAND_0;
  wire [4:0] _T_8;
  wire [3:0] _T_9;
  wire  _T_31;
  wire [3:0] _T_60;
  wire [3:0] _T_61;
  wire [3:0] _T_62;
  wire [3:0] _T_63;
  wire [3:0] _T_64;
  wire [3:0] _T_65;
  wire [3:0] _T_66;
  wire [3:0] csignals_0;
  wire [3:0] _T_67;
  wire [3:0] _T_68;
  wire [3:0] _T_69;
  wire [3:0] _T_70;
  wire [3:0] _T_71;
  wire [3:0] _T_72;
  wire [3:0] _T_73;
  wire [3:0] csignals_1;
  wire  _T_75;
  assign _T_8 = reg$ + 4'h1;
  assign _T_9 = _T_8[3:0];
  assign _T_31 = 4'hf == reg$;
  assign _T_60 = _T_31 ? 4'he : 4'h1;
  assign _T_61 = _T_31 ? 4'hc : _T_60;
  assign _T_62 = _T_31 ? 4'ha : _T_61;
  assign _T_63 = _T_31 ? 4'h8 : _T_62;
  assign _T_64 = _T_31 ? 4'h6 : _T_63;
  assign _T_65 = _T_31 ? 4'h4 : _T_64;
  assign _T_66 = _T_31 ? 4'h2 : _T_65;
  assign csignals_0 = _T_31 ? 4'h0 : _T_66;
  assign _T_67 = _T_31 ? 4'h7 : 4'h1;
  assign _T_68 = _T_31 ? 4'h8 : _T_67;
  assign _T_69 = _T_31 ? 4'h9 : _T_68;
  assign _T_70 = _T_31 ? 4'ha : _T_69;
  assign _T_71 = _T_31 ? 4'hb : _T_70;
  assign _T_72 = _T_31 ? 4'hc : _T_71;
  assign _T_73 = _T_31 ? 4'hd : _T_72;
  assign csignals_1 = _T_31 ? 4'he : _T_73;
  assign _T_75 = reg$ == 4'hf;
  assign io_exit = _T_75;
  assign io_outA = csignals_0;
  assign io_outB = csignals_1;
`ifdef RANDOMIZE
  integer initvar;
  initial begin
    `ifndef verilator
      #0.002 begin end
    `endif
  `ifdef RANDOMIZE_REG_INIT
  _RAND_0 = {1{$random}};
  reg$ = _RAND_0[3:0];
  `endif // RANDOMIZE_REG_INIT
  end
`endif // RANDOMIZE
  always @(posedge clock) begin
    if (reset) begin
      reg$ <= 4'h0;
    end else begin
      reg$ <= _T_9;
    end
  end
endmodule

下4桁がFのときに_T_32が1になり、その後の条件式ではassign _T_60 = _T_31 ? 4'he : 4'h1かもしくわ assign csignals_0 = _T_31 ? 4'h0 : _T_66;では4'h0か4'h1が選ばれる回路になるようです。この二行間の回路は意味をなしてませんので論理合成時に消えるものと思われます。

 雑なまとめ

とりあえずListLookupは危険だなと思いました。chiselの公式Wikiを見れば詳しい挙動が書いてありそうですが、どこに書いてあるか見つけられない。