Twitterでつぶやいてた以下のスレッドに書いてたやつをまとめてみた話。
ある数字Nが表せる状態数のビット幅を取得するとき→log2Ceil(N)
— "だいにんぎょー"って読むことにした (@diningyo) August 21, 2019
その数字Nを表現するために必要なビット数を取得するとき→log2Ceil(N+1)
を使えってソースに書いてあった!(ランダム試験で1敗)
Chisel.util.log2xx
Chiselにはその値を表現するのに必要なビット幅を算出するためのユーティリティとして以下の4種類が用意されている。
System Verilogの$clog2
みたいなの。
- log2Up
- log2Down
- log2Ceil
- log2Floor
上記のメソッドはChisel3.2のSNAPSHOT時点からは各メソッドごとに以下のようにScaladocがちゃんと入っていてソースを見ればどういうものかわかるようになっている(v3.1.8では詳細な説明は存在しない)
/** Compute the log2 of a Scala integer, rounded up. * Useful for getting the number of bits needed to represent some number of states (in - 1). * To get the number of bits needed to represent some number n, use log2Ceil(n + 1). * * Note: can return zero, and should not be used in cases where it may generate unsupported * zero-width wires. * * @example {{{ * log2Ceil(1) // returns 0 * log2Ceil(2) // returns 1 * log2Ceil(3) // returns 2 * log2Ceil(4) // returns 2 * }}} */ object log2Ceil { def apply(in: BigInt): Int = { require(in > 0) (in-1).bitLength } def apply(in: Int): Int = apply(BigInt(in)) }
ただ毎回ソース見るのも面倒なのでまとめておこう、、というのが冒頭のつぶやきであり本記事になる。
サンプルコード書いて調べてみる
だいたい使うときに気にするのって以下の2点くらいなのでi
を1 ~ 16の範囲で変化させて確認した。
- 1を入れた時にどうなるか
- 2のべき乗でどうなるか
その際のコードは以下。
import chisel3._ import chisel3.util._ /** * ただ単にlog2xxを呼んで値をチェックするメインオブジェクト */ object Main extends App { for (i <- 0x1 to 0x10) { println( f"| $i%2d | " + f"${log2Up(i)} | ${log2Up(i + 1)} | " + f"${log2Down(i)} | ${log2Down(i + 1)} | " + f"${log2Ceil(i)} | ${log2Ceil(i + 1)} | " + f"${log2Floor(i)} | ${log2Floor(i + 1)} | " + f"${isPow2(i)} |") } }
各々のメソッドについてi
を入れた場合とi+1
を入れた場合も一緒に確認しておく。というのも3.2-SNAPSHOT時点でlog2Up
/log2Down
に以下のように「0bitのwireが動くまでは廃止しないけど1より大きくなるのがわかってるならlog2Ceilを使って」的なコメントが入っているからだ。
/** Compute the log2 of a Scala integer, rounded up, with min value of 1. * Useful for getting the number of bits needed to represent some number of states (in - 1), * To get the number of bits needed to represent some number n, use log2Up(n + 1). * with the minimum value preventing the creation of currently-unsupported zero-width wires. * * Note: prefer to use log2Ceil when in is known to be > 1 (where log2Ceil(in) > 0). * This will be deprecated when zero-width wires is supported. * * @example {{{ * log2Up(1) // returns 1 * log2Up(2) // returns 1 * log2Up(3) // returns 2 * log2Up(4) // returns 2 * }}} */```
log2xxの計算結果一覧
結果は以下のようになった(表がでかくなるのでlog2Up等のlog2は省きました)
i | Up(i) | Up(i+1) | Down(i) | Down(i+1) | Ceil(i) | Ceil(i+1) | Floor(i) | Floor(i+1) |
---|---|---|---|---|---|---|---|---|
1 | 1 | 1 | 1 | 1 | 0 | 1 | 0 | 1 |
2 | 1 | 2 | 1 | 1 | 1 | 2 | 1 | 1 |
3 | 2 | 2 | 1 | 2 | 2 | 2 | 1 | 2 |
4 | 2 | 3 | 2 | 2 | 2 | 3 | 2 | 2 |
5 | 3 | 3 | 2 | 2 | 3 | 3 | 2 | 2 |
6 | 3 | 3 | 2 | 2 | 3 | 3 | 2 | 2 |
7 | 3 | 3 | 2 | 3 | 3 | 3 | 2 | 3 |
8 | 3 | 4 | 3 | 3 | 3 | 4 | 3 | 3 |
9 | 4 | 4 | 3 | 3 | 4 | 4 | 3 | 3 |
10 | 4 | 4 | 3 | 3 | 4 | 4 | 3 | 3 |
11 | 4 | 4 | 3 | 3 | 4 | 4 | 3 | 3 |
12 | 4 | 4 | 3 | 3 | 4 | 4 | 3 | 3 |
13 | 4 | 4 | 3 | 3 | 4 | 4 | 3 | 3 |
14 | 4 | 4 | 3 | 3 | 4 | 4 | 3 | 3 |
15 | 4 | 4 | 3 | 4 | 4 | 4 | 3 | 4 |
16 | 4 | 5 | 4 | 4 | 4 | 5 | 4 | 4 |
回路のパラメタライズをやるときにlog2Ceil
を使った結果ビット幅がゼロになるというケースに何度か出くわした。先ほどのソースのコメントにもあるとおり、現状ビット幅が0のwireは作れないので注意が必要だ。
改めてまとめてみての自分的使い分けは以下の感じ。
- アドレスのビット幅→
log2Ceil(N)
- これは容量1KBのRAMと言った時には1024の状態があればよくて、その場合は0~1023の範囲で表せるから
- ポート数やらモジュールの数→
log2Up(N)
もしくはlog2Ceil(N+1)
- こっちはNという数字自体に意味があるので、その数字自体を表現できるビット幅が必要になる
- ソースのコメント的には
log2Up
はいずれ消えそうなので、log2Ceil(N+1)
で揃えておくのが良さそう
ということで、これからは迷ったらこの表を参照すればOK!という自分メモでした。