ハードウェアの気になるあれこれ

技術的に興味のあることを調べて書いてくブログ。主にハードウェアがネタ。

Chisel Bootcamp - Module3.5(3) - グレイコードのエンコーダとデコーダ

スポンサーリンク

前回のChiselの記事ではFIRRTLを可視化するツールdiagrammerを試してみた内容をまとめた。

www.tech-diningyo.info

今回はしばらく進めていなかったChisel-Bootcampに戻り、Module3.5の残りを見ていく。 オブジェクト指向プログラミングをChiselに適用すると??という話だ。

Chiselでオブジェクト指向プログラミング

とそれらしいタイトルをつけては見たが、実はこれまでに山ほどChiselでオブジェクト指向プログラミングはやってきているという話。 Bootcampの説明を借りると

  • Chiselのモジュール → class Moduleを継承してるでしょ?
  • ChiselのIO → Bundle使ってるよね??
  • Chiselのデータ型(UIntとかBool) → あれはDataのサブクラスだよ?

になる。 因みに、一個だけ気になるキーワードがあるので自分向けのメモの意味も含めて以下の部分だけ引用しておく(気になったのはRecordってやつ、そのうち調べよう)。

全てのChiselのIOはBundleを継承して作られている(もしくは特別な状況下ではBundleのサブタイプRecord

Module

例題:グレイエンコーダとデコーダ

ここからは例を使って見ていく。 Chisel-Bootcampの順番とは変えて、まずはグレイコードの生成部分を。

import scala.math.pow

// create a module
class GrayCoder(bitwidth: Int) extends Module {
  val io = IO(new Bundle{
    val in = Input(UInt(bitwidth.W))
    val out = Output(UInt(bitwidth.W))
    val encode = Input(Bool()) // decode on false
  })

  when (io.encode) { //encode
    io.out := io.in ^ (io.in >> 1.U)
  } .otherwise { // decode, much more complicated
    io.out := Seq.fill(log2Ceil(bitwidth))(Wire(UInt(bitwidth.W))).zipWithIndex.fold((io.in, 0)){
      case ((w1: UInt, i1: Int), (w2: UInt, i2: Int)) => {
        w2 := w1 ^ (w1 >> pow(2, log2Ceil(bitwidth)-i2-1).toInt.U)
        (w2, i1)
      }
    }._1
  }
}

なんか急に複雑になった気がしないでもない。特にotherwiseの部分。とりあえず、入力のio.encodeの状態に応じてエンコード/デコードの処理が切り替えられるようになっている。 エンコード部分は特に難しいことは何もなく、ただ入ってきた2進数データをグレイコードに変換しているだけになっている。 で、やたら複雑にみえるデコード部分を細かく見てみよう。

io.out := Seq.fill(log2Ceil(bitwidth))(Wire(UInt(bitwidth.W))).zipWithIndex.fold((io.in, 0)){
  case ((w1: UInt, i1: Int), (w2: UInt, i2: Int)) => {
    w2 := w1 ^ (w1 >> pow(2, log2Ceil(bitwidth)-i2-1).toInt.U)
    (w2, i1)
  }
}._1
  1. Seq.fill(log2Ceil(bitwidth))(Wire(UInt(bitwidth.W)))a
    1. Seq.fill(N)でNに指定したサイズのSeqを生成して
    2. 中身をビット幅UInt(bitwidth.W)Wireで初期化
  2. .zipWithIndex
    1. 作ったWireの入ったSeqと要素数をタプルにしたSeqを生成
      1. 型で書くとSeq[(UInt, Int)]の状態
  3. .fold((io.in, 0))
    1. 最初の要素が(io.in, 0)として、後続のブロック式部分をfoldで適用
      1. case ((w1: UInt, i1: Int), (w2: UInt, i2: Int))にマッチした際に後続のブロック式を適用
        1. foldで計算が行われるためw1/w2Seq.fillで作成したWireio.ini1/i2zipWithIndexで作成したInt型のインデックス
        2. 最初の演算だとw1==io.in, i1==0となり、この2つデータから計算された値がw2アサインされる
      2. (w2, i1)がこのブロック式の戻り値
  4. }._1
    1. ケース文部分のブロック式の戻り値が(w2, i2)のタプルになっており、そのタプルの1番目の要素(==w2)をio.outアサインする
テストコード

早速これをテストしてみよう、、というコードが以下になる。

// test our gray coder
val bitwidth = 4
Driver(() => new GrayCoder(bitwidth)) {
  c => new PeekPokeTester(c) {
  
    def toBinary(i: Int, digits: Int = 8) =
      String.format("%" + digits + "s", i.toBinaryString).replace(' ', '0')

    println("Encoding:")
    for (i <- 0 until pow(2, bitwidth).toInt) {
      poke(c.io.in, i)
      poke(c.io.encode, true)
      step(1)
      println(s"In = ${toBinary(i, bitwidth)}, Out = ${toBinary(peek(c.io.out).toInt, bitwidth)}")
    }
    
    println("Decoding:")
    for (i <- 0 until pow(2, bitwidth).toInt) {
      poke(c.io.in, i)
      poke(c.io.encode, false)
      step(1)
      println(s"In = ${toBinary(i, bitwidth)}, Out = ${toBinary(peek(c.io.out).toInt, bitwidth)}")
    }
  }
}

今回は特にexpectによる期待値比較は行われていないが、エンコードとデコードのテストがそれぞれ実施されているので、出力を見比べれば結果が正しいことは確認できる。

[info] [0.000] Elaborating design...
[info] [0.632] Done elaborating.
Total FIRRTL Compile Time: 219.7 ms
Total FIRRTL Compile Time: 19.1 ms
End of dependency graph
Circuit state created
[info] [0.001] SEED 1551359800217
[info] [0.002] Encoding:
[info] [0.005] In = 0000, Out = 0000
[info] [0.006] In = 0001, Out = 0001
[info] [0.009] In = 0010, Out = 0011
[info] [0.011] In = 0011, Out = 0010
[info] [0.012] In = 0100, Out = 0110
[info] [0.013] In = 0101, Out = 0111
[info] [0.014] In = 0110, Out = 0101
[info] [0.016] In = 0111, Out = 0100
[info] [0.018] In = 1000, Out = 1100
[info] [0.019] In = 1001, Out = 1101
[info] [0.021] In = 1010, Out = 1111
[info] [0.025] In = 1011, Out = 1110
[info] [0.034] In = 1100, Out = 1010
[info] [0.037] In = 1101, Out = 1011
[info] [0.038] In = 1110, Out = 1001
[info] [0.039] In = 1111, Out = 1000
[info] [0.040] Decoding:
[info] [0.047] In = 0000, Out = 0000
[info] [0.048] In = 0001, Out = 0001
[info] [0.050] In = 0010, Out = 0011
[info] [0.056] In = 0011, Out = 0010
[info] [0.058] In = 0100, Out = 0111
[info] [0.059] In = 0101, Out = 0110
[info] [0.060] In = 0110, Out = 0100
[info] [0.063] In = 0111, Out = 0101
[info] [0.064] In = 1000, Out = 1111
[info] [0.066] In = 1001, Out = 1110
[info] [0.067] In = 1010, Out = 1100
[info] [0.070] In = 1011, Out = 1101
[info] [0.072] In = 1100, Out = 1000
[info] [0.074] In = 1101, Out = 1001
[info] [0.075] In = 1110, Out = 1011
[info] [0.076] In = 1111, Out = 1010
test cmd3HelperGrayCoder Success: 0 tests passed in 37 cycles taking 0.087356 seconds
[info] [0.079] RAN 32 CYCLES PASSED

AsyncFIFO

上記でグレイコードのエンコーダとデコーダが出来た。ということでグレイコードを使った例ということで非同期のFIFO内での使われ方が部分的にではあるがこれも一応載せておく。 とはいえ、とりあえず実装例、、というよりは先程のグレイコードのモジュールをどうやってインスタンス&結線するよーという例でしかないのだが。。

class AsyncFIFO(depth: Int = 16) extends Module {
  val io = IO(new Bundle{
    // write inputs
    val write_clock = Input(Clock())
    val write_enable = Input(Bool())
    val write_data = Input(UInt(32.W))
    
    // read inputs/outputs
    val read_clock = Input(Clock())
    val read_enable = Input(Bool())
    val read_data = Output(UInt(32.W))
    
    // FIFO status
    val full = Output(Bool())
    val empty = Output(Bool())
  })
  
  // add extra bit to counter to check for fully/empty status
  assert(isPow2(depth), "AsyncFIFO needs a power-of-two depth!")
  val write_counter = withClock(io.write_clock) { Counter(io.write_enable && !io.full, depth*2)._1 }
  val read_counter = withClock(io.read_clock) { Counter(io.read_enable && !io.empty, depth*2)._1 }
  
  // encode
  val encoder = new GrayCoder(write_counter.getWidth)
  encoder.io.in := write_counter
  encoder.io.encode := true.B
  
  // synchronize
  val sync = withClock(io.read_clock) { ShiftRegister(encoder.io.out, 2) }
  
  // decode
  val decoder = new GrayCoder(read_counter.getWidth)
  decoder.io.in := sync
  decoder.io.encode := false.B
  
  // status logic goes here
  
}

上記の回路をちゃんと組んでみるのもいい勉強になるかも。 時間を見つけて試してみたい。