FPGA開発日記

カテゴリ別記事インデックス https://msyksphinz.github.io/github_pages , English Version https://fpgadevdiary.hatenadiary.com/

FIRRTLに入門する (18. 多次元配列に対する処理の検討)

https://raw.githubusercontent.com/freechipsproject/firrtl/master/doc/images/firrtl_logo.svg?sanitize=true

前回のLowerTypeの実装では、多次元配列に関する処理が抜けていた。このため、以下のようなFIRのコードをコンパイルするとエラーが発生する。

  • VecBundle.fir
circuit VecBundle :
  module VecBundle :
    input in: UInt<32>[3][4]
    input sel: UInt<2>
    output out : UInt<32>[3]

    out <= in[sel]
./utils/bin/firrtl -td regress -i ./regress/VecBundle.fir -X sverilog -ll trace 2>&1 | tee VecBundle.log
Exception in thread "main" firrtl.FirrtlInternalException: Internal Error! trying to write unsupported type in the Verilog Emitter: VectorType(VectorType(UIntType(IntWidth(32)),3),4)

ぬーん、どうやら最終的なSystemVerilogEmitterのフェーズでVectorTypeのしかも入れ子になっている記述を想定していなかったようだ。このSystemVerilogEmitterの段階でのFIRのツリーはここまで変換されている。

circuit VecBundle :
  module VecBundle :
    input in : UInt<32>[3][4]
    input sel : UInt<2>
    output out : UInt<32>[3]

    out <= in[sel]

まあこれは上記の入力ファイルと同じなので変換されているとは言わない...

そこで、SystemVerilogEmitter独自のstringifyを作って、このツリーを最終的に文字列に落とし込める量にしたいと思う。そのためには、SystemVerilogEmitterにoverrideしたstringifyを追加する必要がありそうだ。

  • src/main/scala/firrtl/SystemVerilogEmitter.scala
  override def stringify(tpe: VectorType): String = {
    val ground_type = tpe.tpe
    val elem_type = ground_type match {
      case (t: VectorType) => {
        val wx = bitWidth(tpe) - 1
        val field_str = if (wx > 0) s"[$wx:0]" else ""
        val str_element = tpe.tpe match {
          case tpe_elem: VectorType => stringify(tpe_elem)
          case tpe_elem: GroundType => stringify(tpe_elem)
        }
        str_element + field_str
      }
      case (_: UIntType | _: SIntType | _: AnalogType) =>
        val wx = bitWidth(tpe) - 1
        if (wx > 0) s"[$wx:0]" else ""
      case ClockType | AsyncResetType | AsyncResetNType => ""
      case _ => throwInternalError(s"trying to write unsupported type in the Verilog Emitter: $tpe")
    }
    elem_type + s"[${tpe.size}]"
  }

case (t: VectorType)の中身をもう少し噛み砕いている。つまり、VectorTypeの中身をさらに解析し、さらにVectorTypeならばstringify(tpe: VectorType)を呼び出し、GroundTypeならばstringify(tp: GroundType)を呼び出すという算段にしている。これでVectorType入れ子も上手く行くはずだ。

circuit VecBundle :
  module VecBundle :
    input in : UInt<32>[3][4]
    input sel : UInt<2>
    output out : UInt<32>[3]

    out <= in[sel]

だがこれでは少し足りない。なんだかVectorのビット幅の挙動がおかしい。

EmittedVerilogCircuitAnnotation(EmittedVerilogCircuit(VecBundle,module VecBundle(
  input logic  [95:0][383:0] in,
  input logic  [1:0]         sel,
  output logic [95:0]        out
);
  assign out = in[sel];
endmodule

これは大変なことになっているので、bitwidth()の計算を修正する。

diff --git a/src/main/scala/firrtl/Utils.scala b/src/main/scala/firrtl/Utils.scala
index 0b26e6a1..38495694 100644
--- a/src/main/scala/firrtl/Utils.scala
+++ b/src/main/scala/firrtl/Utils.scala
@@ -53,7 +53,8 @@ object getWidth {
 object bitWidth {
   def apply(dt: Type): BigInt = widthOf(dt)
   private def widthOf(dt: Type): BigInt = dt match {
-    case t: VectorType => t.size * bitWidth(t.tpe)
+    // case t: VectorType => t.size * bitWidth(t.tpe)
+    case t: VectorType => t.size
     case t: BundleType => t.fields.map(f => bitWidth(f.tpe)).foldLeft(BigInt(0))(_+_)
     case GroundType(IntWidth(width)) => width
     case t => Utils.error(s"Unknown type encountered in bitWidth: $dt")

あれー、今度は最後の32ビットが表示されない。なんでだ。

module VecBundle(
  input logic  [2:0][3:0] in,
  input logic  [1:0]      sel,
  output logic [2:0]      out
);
  assign out = in[sel];
endmodule

もうすこし改造を加える。ビットサイズの条件を付け加えた。

diff --git a/src/main/scala/firrtl/SystemVerilogEmitter.scala b/src/main/scala/firrtl/SystemVerilogEmitter.scala
index 77b50432..dcee604a 100644
--- a/src/main/scala/firrtl/SystemVerilogEmitter.scala
+++ b/src/main/scala/firrtl/SystemVerilogEmitter.scala
@@ -24,23 +24,28 @@ class SystemVerilogEmitter extends VerilogEmitter with Emitter {

   override def stringify(tpe: VectorType): String = {
     val ground_type = tpe.tpe
     val elem_type = ground_type match {
       case (t: VectorType) => {
-        val wx = bitWidth(tpe) - 1
-        val field_str = if (wx > 0) s"[$wx:0]" else ""
+        val wx = sv_bitWidth(tpe) - 1
+        val field_str = if (wx > 0) s"[$wx]" else ""
         val str_element = tpe.tpe match {
           case tpe_elem: VectorType => stringify(tpe_elem)
           case tpe_elem: GroundType => stringify(tpe_elem)
         }
         str_element + field_str
       }
-      case (_: UIntType | _: SIntType | _: AnalogType) =>
-        val wx = bitWidth(tpe) - 1
-        if (wx > 0) s"[$wx:0]" else ""
+      case (_: UIntType | _: SIntType | _: AnalogType) => {
+        val wx = sv_bitWidth(tpe) - 1
+        val elem_w = sv_bitWidth(ground_type) - 1
+        val elem_str = if (elem_w > 0) s"[${elem_w}:0]" else ""
+        val vec_str = if (wx > 0) s"[$wx]" else ""
+        elem_str + vec_str
+      }
       case ClockType | AsyncResetType | AsyncResetNType => ""
       case _ => throwInternalError(s"trying to write unsupported type in the Verilog Emitter: $tpe")
     }
-    elem_type + s"[${tpe.size}]"
+    elem_type
   }
object sv_bitWidth {
  def apply(dt: Type): BigInt = widthOf(dt)
  private def widthOf(dt: Type): BigInt = dt match {
    case t: VectorType => t.size
    case t: BundleType => t.fields.map(f => bitWidth(f.tpe)).foldLeft(BigInt(0))(_+_)
    case GroundType(IntWidth(width)) => width
    case t => Utils.error(s"Unknown type encountered in bitWidth: $dt")
  }
}

再度Verilogを生成してみる。不格好だが何とか形になっている。

module VecBundle(
  input logic  [31:0][2][3] in,
  input logic  [1:0]        sel,
  output logic [31:0][2]    out
);
  assign out = in[sel];
endmodule

fork/joinが可能なchiselテスト環境chisel-testers2を試す

Chisel-Testers2のアナウンスが流れていた。時間が経っていたが試してみようと思う。

ChiselにはすでにIOTestersというテスト環境があるが、ぶっちゃけこれはあまり使いやすくない。 chisel-testers2は以下の改良(というか特徴)が加わっており、使いやすくなるように改善が行われている。

www.chisel-lang.org

紹介分から抜粋する。

Overview

Testers2 is a test harness for Chisel-based RTL designs, currently supporting directed testing (all test stimulus manually specified - no constrained random and > coverage-driven flows). Testers2 emphasizes tests that are lightweight (minimizes boilerplate code), easy to read and write (understandability), and compose (for better test code reuse).

The core primitives are similar to nonsynthesizable Verilog: input pin assignment (poke), pin value assertion (expect), and time advance (step). Threading concurrency is also supported with the use of fork and join, and concurrent accesses to wires are checked to prevent race conditions.

Migrating from chisel-testers

The core abstractions (poke, expect, step) are similar to chisel-testers, but the syntax is inverted: instead of doing tester.poke(wire, value) with a Scala number value, in testers2 you would write write.poke(value) with a Chisel literal value. Furthermore, as no reference to the tester context is needed, test helper functions can be defined outside a test class and written as libraries.

Currently, this should support all the functionality that was in chisel-testers, and provides additional features. This project is meant to supersede chisel-testers, and eventually may become a default part of chisel3.

Test cases written in chisel-testers cannot be directly used in testers2, as the syntax is significantly different.

Testers2の特徴としては、基本的なテストの方法はIOTestersと変わらずpoke(ピンの値割り当て)、expect(ピンの値の比較)、step(時間を進める)であるが、スレッドを導入することにより並列なテスト記述ができるようになる。つまり、forkjoinが使えるようになり、テストがより記述しやすくなるのである。

さらにテストの記述方法としては、これまでのIOTestersではtester.poke(wire, value)と書いていたものを、wire.poke(value)と記述することができるようになり、信号線に対して直接pokeを送ることができるようになる。これは便利かも。

という訳で試してみる。まずはtesters2のリポジトリ全体をダウンロードしてリグレッションテストから。

git clone https://github.com/ucb-bar/chisel-testers2.git
cd chisel-testers2.git
sbt test

なんとなくPassしたぞ。下記のキャプチャ画像は最後のVerilatorテストのみ。

f:id:msyksphinz:20191207115007p:plain
chisel-testers2のsbt test実行結果。

次に、自分でデザインを書いてみる。シフトレジスタを設計した。

f:id:msyksphinz:20191207142114p:plain
4レベルシフトレジスタの設計とforkを使ったテスト
  • src/main/scala/iotesters2-simple/iotesters2-simple.scala
package iotesters2_simple

import chisel3._
import chisel3.util._

class ShiftRegister(length: Int) extends Module {
  val io = IO(new Bundle {
    val in  = Input(UInt(32.W))
    val out = Output(UInt(32.W))
  })

  require(length >= 1)

  val shift_reg = Reg(Vec(length, UInt(32.W)))
  shift_reg(0) := io.in
  for(i <- 1 until length) {
    shift_reg(i) := shift_reg(i-1)
  }
  io.out := shift_reg(length-1)
}

次に、テストを書いた。と言っても、サンプルプログラムをほぼコピーしたのだが...

https://github.com/ucb-bar/chisel-testers2/blob/master/src/test/scala/chisel3/tests/ShiftRegisterTest.scalagithub.com

  • src/test/scala/iotesters2-simple/iotesters2-simple.scala
package iotesters2_simple

import org.scalatest._

import chisel3._
import chisel3.tester._

class ShiftRegisterTest extends FlatSpec with ChiselScalatestTester with Matchers {
  behavior of "Testers2"

  it should "test shift registers with abstractions" in {
    // TODO: this actually relies on total thread ordering

    def shiftTest(in: UInt, out: UInt, clk: Clock, value: UInt) {
      timescope {
        in.poke(value)
        clk.step(1)
      }
      clk.step(3)
      out.expect(value)
    }

    test(new ShiftRegister(4)) { c =>
      fork { shiftTest(c.io.in, c.io.out, c.clock, 42.U) }
      c.clock.step(1)
      fork { shiftTest(c.io.in, c.io.out, c.clock, 43.U) }
      c.clock.step(1)
      fork { shiftTest(c.io.in, c.io.out, c.clock, 44.U) }
      c.clock.step(1)
      fork { shiftTest(c.io.in, c.io.out, c.clock, 45.U) }
      c.clock.step(1)
      fork { shiftTest(c.io.in, c.io.out, c.clock, 46.U) }
      c.clock.step(1)
      fork { shiftTest(c.io.in, c.io.out, c.clock, 47.U) }.join
    }
  }
}

この時に注意すべきことなのだが、build.sbtに記述するchisel3のバージョンとchisel3-testers2のバージョンを調整する必要がある。

  • build.sbt
...
val defaultVersions = Map(
  // "chisel3" -> "3.2.+",
  "chisel3" -> "3.3-SNAPSHOT",
  "chisel-iotesters" -> "1.3.+",
  "chisel-testers2" -> "0.2-SNAPSHOT",
  "dsptools" -> "1.1.+"
)

libraryDependencies ++= (Seq("chisel3", "chisel-iotesters", "dsptools", "chisel-testers2").map {
  dep: String => "edu.berkeley.cs" %% dep % sys.props.getOrElse(dep + "Version", defaultVersions(dep)) })
...

テストを実行する。

sbt 'testOnly iotesters2_simple.ShiftRegisterTest'
...
Total FIRRTL Compile Time: 516.1 ms
file loaded in 0.0921011 seconds, 13 symbols, 10 statements
test ShiftRegister Success: 0 tests passed in 11 cycles in 0.078559 seconds 140.02 Hz
[info] ShiftRegisterTest:
[info] Testers2
[info] - should test shift registers with abstractions
[info] ScalaTest
[info] Run completed in 2 seconds, 224 milliseconds.
[info] Total number of tests run: 1
[info] Suites: completed 1, aborted 0
[info] Tests: succeeded 1, failed 0, canceled 0, ignored 0, pending 0
[info] All tests passed.
[info] Passed: Total 1, Failed 0, Errors 0, Passed 1
[success] Total time: 6 s, completed 2019/12/07 13:11:58
f:id:msyksphinz:20191207141258p:plain
chisel-testers2を使用してテストを記述した結果

テスト結果は問題ない様だ。forkによるテストも問題なく実行できた。

本当に私の人生を変えた技術書10選

年末ですね。年末に技術っぽいことを書いても誰も見ていないので、どうでもいいことを書こうと思います。

皆さん技術書は好きですか?好きですよね。読みもしないのに技術書典なんかに大挙して押しかけて、結局積読が増えていく。積んでいるとなんか落ち着くのかもしれません。

私は現在ハードウェア関連の技術者として働いているわけですが、短い人生の中で読んだ技術書の中で、本当に私の人生を変えてしまった技術書を思い出しながら紹介してみたいと思います。

あらかじめ断っておきますが、「名著」や「良い本」を紹介するのではなく、あくまでも私の人生を変えた本です。逆にいうと、あまり名著は出てきません。名著の紹介はすでにいろんなところでやられているので、そちらを見ていただければ。

1. 図解で分かるPCアーキテクチャのすべて(初版)

〈最新〉図解でわかる PCアーキテクチャのすべて

〈最新〉図解でわかる PCアーキテクチャのすべて

確か私が中学生の時くらいに読んでいた気がする。Amazonのリンクは改訂版ですが、もう初版は売っていない。初版はだPentium 2だとかPentium IIIだとか、そのあたりが解説してあった。

この本の凄いところは、コンピュータアーキテクチャという難しそうな分野を、非常に多くの図を追加ながら簡単にかみ砕いて紹介してくれるところだ。 CPUの簡単な仕組みだとか、レジスタ・メモリ・ハードディスク、バスについて、などなど。頭の悪い私のような人でも簡単に読み進めることができ、しかもわかりやすい。この本を読んで「コンピュータアーキテクチャオモシロ!」と思ってしまったのが私の人生の分岐点だった気がする。

つまり何が言いたいかというと、別に名著を読む必要はなくて、最初にとっつきやすい入門書や最初のハードルが低ければ、興味を持った多くの若者が入ってきやすい。 その門を広げるのが教育機関のはずなのだが、大学の先生とかは研究しか考えておらず教育に熱心でない人が多いのでそのあたりは不安に思っている。例えば授業がめっちゃ面白くない先生の研究室にわざわざ入って研究したいという学生がどれくらいいるのだろうか?自分の分野の門徒を狭めるような授業をするような先生は、とっとと教育研究機関など辞めてしまって、研究機関に転職した方が良い。

  • 2020年に読むべきは?

今のところ、ここまで明快なPCアーキテクチャの技術書を私は知らない。 中学生にも分かるレベルでPCアーキテクチャを図解で説明してくれる書籍を、誰か執筆して欲しいものだ。

2. マイクロプロセッサ・アーキテクチャ入門

確か高校生くらいの時に読んでいた。例えば情報処理技術者試験の教科書を読んだりすると、CPUとは「フェッチ→デコード→実行」くらいにしか書いていないのだが、もう少し噛み砕いて、詳細なパイプラインを解説してくれる。あまりすべてを理解できていた記憶はないのだが、うっすらと、「パイプラインカッコいい」だとか「マルチコアすごい」などと思っていた気がする。小並感だな。

ちなみに、この本を読んで十数年後に、ずっと尊敬していた著者の中森章さんに実際にお会いする機会まで恵まれた。同じ分野でずっと活動していると、うれしいアクシデントも発生するものである。

  • 2020年に読むべきは?

このレベルでマイクロプロセッサのアーキテクチャについて纏められる人は、後にも先にも中森章さんとHisa Andoさんくらいしかいないんじゃないかと思うが、今だったらHisa Ando氏の「コンピュータアーキテクチャ技術入門」「高性能コンピュータ技術の基礎」が類似するだろうか。いずれにしろ、Hisa Ando氏の書籍はマイクロプロセッサアーキテクトを志す人には必読の書籍だと思う。

コンピュータアーキテクチャ技術入門 ~高速化の追求×消費電力の壁 (WEB+DB PRESS plus)

コンピュータアーキテクチャ技術入門 ~高速化の追求×消費電力の壁 (WEB+DB PRESS plus)

  • 作者:Hisa Ando
  • 出版社/メーカー: 技術評論社
  • 発売日: 2014/05/01
  • メディア: 単行本(ソフトカバー)

高性能コンピュータ技術の基礎

高性能コンピュータ技術の基礎

3. 作りながら学ぶコンピュータアーキテクチャ

作りながら学ぶコンピュータアーキテクチャ

作りながら学ぶコンピュータアーキテクチャ

確か大学生くらいの時に読んでいた。今でこそCPUを設計しようという書籍は山ほど出ているが、当時はほとんど存在せずかなり貴重な存在だった気がする。SFLとパルテノンというなんじゃそりゃ?みたいな言語を使って解説してあるのだが、そこはとりあえずVHDLVerilogに置き換えて実装していた。

この本の凄いところは、これまで教科書でしか見たことのなかったパイプラインの実装方法やフォワーディングなどの基本的なパイプラインの実装方法きちんと実ソースで示してくれるところだ。ステップバイステップで解説してくれるのでありがたい。この本を読みながら、自作MIPSプロセッサをたくさん作ってFPGAに乗せて遊んでいた。間違いなく私の人生を変えてしまった本である。

  • 2020年に読むべきは?

2010年以降、雨後の筍のように発行され始めたCPU自作系の書籍は、別にどれも変わらないと思う。 みんなVerilogを書いてシミュレーションしてFPGAで動かす、基本的な流れは一緒なので好きなのを読めばよいと思う。

4. Modern Compiler Implementation in C

Modern Compiler Implementation in C

Modern Compiler Implementation in C

  • 作者:Andrew W. Appel
  • 出版社/メーカー: Foundation Books
  • 発売日: 2007/12/01
  • メディア: ペーパーバック

いわゆるタイガーブックである。コンパイラと言えば、ドラゴンブック・タイガーブックというのがあるが、私はタイガーブックから入った。なぜかというと当時東大生?だったか、誰かがブログでタイガーブックを読み始めた、というのがあったので私も読んでみようと思ったからだ。確か私は当時大学生だった気がする。そして、私が読み終えた1年後くらいに日本語版が発行された。とても悲しい。

実際、これも実際に手を動かすタイプの本で非常に面白い。この本のターゲットはフロントエンドが独自言語なのだが、最終的に私はC言語に移植して動かしていた気がする。バックエンドをMIPSにして、FPGA上で自分のコンパイルしたコードを自作MIPSプロセッサに流し込んで動かしていた。

結局、この時に作ったコンパイラを最後までもっていって、修論でも独自アーキテクチャ向けに改造して論文を書いていたので、間違いなく私の人生を変えた一冊だ。この本を読んだおかげで、コンパイラという存在があまり怖くなくなった。

  • 2020年に読むべきは?

最近本格的なコンパイラの書籍はご無沙汰しているのでアレだが、今だったらRui Ueyama氏のコンパイラの資料が強いのだろうか。 まあ私もちゃんと読んでいないので、まずは自分が読め、というところだろうか...

www.sigbus.info

5. Structure and Interpretation of Computer Programming

Structure and Interpretation of Computer Programs

Structure and Interpretation of Computer Programs

なぜかこの辺から名著が登場し始める。いわゆるSICPである。これも大学生の頃に読んでいた。本文の中に問題が埋め込まれていて、これを1つ1つ読み解いていた気がする。英語だし分厚いので時間がかかるのだが、回答をどこかのブログに書いて進めていたら、どこかの韓国人の人が熱心にそれを見てコメントしてくれていたのが懐かしい。

この本は、英語の本を一冊やりきるということの満足感を教えてくれるという意味で大きな一冊だ。 多くの人が手を付けていたし、私にも頑張れば読めるだろ、という気分で読み始めたのだが、後半のインタプリタコンパイラの所は完全に理解できたかというと曖昧な部分が多い。 ただしこの本を完了したおかげで、関数型言語は怖くなくなったし、論理型プログラミング言語が授業で登場したときも全く問題なく受け入れらた。字句解析とかの実験も難なくできた。自分、やればできるじゃんという自信をつけさせてくれた一冊。

  • 2020年に読むべきは?

別に関数プログラミングを進めたいわけではなく、英語の本を一冊読むと、少しだけ人生変わるよというだけの話のなので、好きなのを自分で読めばよいと思う。

6. Advanced FPGA Design

Advanced FPGA Design: Architecture, Implementation, and Optimization (Wiley - IEEE)

Advanced FPGA Design: Architecture, Implementation, and Optimization (Wiley - IEEE)

  • 作者:Steve Kilts
  • 出版社/メーカー: Wiley-IEEE Press
  • 発売日: 2007/06/29
  • メディア: ハードカバー

たぶんこれもFPGA界隈のエンジニアにとっては有名。大学生の頃に読んでいた。FPGAはすでに好きでいろいろ遊んでいたのだけれども、この本を読んで「高性能・小面積・低消費電力」のデザインを設計するためにはどうすればよいか、ということを考えるようになった。FPGA向けのデザインはたくさん勉強したし、いろんな方式で合成をしてどんな回路ができるか確認した。Verilogの書き方はたくさん覚えた。

  • 2020年に読むべきは?

FPGA開発日記」のくせにFPGAからご無沙汰になって久しいが、最近はFPGA自体の性能向上によりこれらの技術はあまり重要視されないのだろうか?どちらかというと「FPGAに上手くはめ込む」という技術より「FPGAの上で何を作り込むか?」に重要性がシフトしてきている気がするので、マニアックなFPGA技術が知りたい人は読めばよいと思う。

私の人生を変えるのに少し役に立った技術書たち

列挙してみると10冊にならなかったので、お茶を濁す

7. 月刊アスキー (96 ~ 97年代)

月刊 ASCII (アスキー) 2006年 08月号 [雑誌]

月刊 ASCII (アスキー) 2006年 08月号 [雑誌]

  • 作者:
  • 出版社/メーカー: アスキー
  • 発売日: 2006/07/18
  • メディア: 雑誌

私のコンピュータの基本的な知識を身につけさせてくれた雑誌。父親が買っていたものを読んでいた。 どうでもいいが、当時の月刊アスキーは1~12を示す月の番号を2進数で表記してあった。 当時は全く気が付かなかったけれども、これに気が付くのにはもう少し時間がかかった。

プログラミングの特集もあったし(当時は全く理解できなかったが)、ロードテストといういろんなPCを動かして試してみる、というのは編集者の日本語力にも差が出て非常に面白かった。あの時に大好きだった編集者が、月刊アスキーを経て別のウェブ雑誌の編集者になり、最終的にGoogleを経てVCの偉い人になっている経歴をずっと眺めていると、何となく彼も成長したな―と思っている。まあ私の方がずっと年下だけど。

今でも覚えている特集記事で「このIT語を知れ!」というものがあった。2000年代の特集で最新の技術紹介がなされているのだが、すでに「ペーパーライクディスプレイ」という名前で曲がるディスプレイの要素技術について紹介があった。その時の衝撃は鮮明に覚えている。実際に実用化させるまで、20年の歳月がかかるとは思ってもみなかったが。

8. FM-77のマニュアル

FM-77というと富士通の作った非常に古いコンピュータ。この辺の話をするとおそらく昔のPCオタクのような老人が生えてきて要らない情報を寄こしてくれるのだが、そんなものは全く不要なので省略。

FM-77のマニュアルは「本体はおまけ、マニュアルに価値あり」といわれるくらいに充実していて、確かこれでBASICを勉強しながら音楽とか鳴らして遊んでいた気がする。たぶん人生で一番最初に作ったプログラム。

9-10. パタヘネ・ヘネパタ

これは人生を変えた本というか、人生が変わった後に読んだ本なので省略。

というか、ヘネパタをいきなり読んで人生が変わってしまうような天才の人は、いきなり医学書とか読んで医者になった方が世界にとって有益な気がする。


というわけでつまり何が言いたいかというと、人生を変える技術書は意外と名著ではなく、魅力的な入門書だったり、初心者にもわかりやすく書いていある本であることが多いと思う。入門書を読んでから「これオモシロ!」と思ったら名著を買えばよろしい。いきなり分厚い本を買うと疲れてしまう。

しかしこうやって振り返ってみると学生の時は技術書をたくさん読んでいるが、社会人になってあまり読まなくなった。 これは勉強量が減っているのでは?精進しなければ。

FIRRTLに入門する (17. LowerType時に配列を分解せずにBundleを分解する手法の調査(3))

https://raw.githubusercontent.com/freechipsproject/firrtl/master/doc/images/firrtl_logo.svg?sanitize=true

LowerTypeの調査を続けている、前回の記事からずいぶんと時間が経ってしまったが、Bundleを分解する方法について調査している。つまり、FIRRTLで以下のように記述されているコードを、Vectorを分解せずにBundleのみ分解するということである。

circuit VecBundle :
  module VecBundle :
    input in: { a : UInt<32>, b : UInt<32> }[4]
    input sel: UInt<2>
    output out : { a : UInt<32>, b : UInt<32> }

    out.a <= in[sel].a
    out.b <= in[sel].b

上記のFIRのコードを分解する。下記のようになれば良い。

circuit VecBundle :
  module VecBundle :
    input in_a : UInt<32>[4]
    input in_b : UInt<32>[4]
    input sel : UInt<2>
    output out_a : UInt<32>
    output out_b : UInt<32>

    skip
    out_a <= in_a[sel]
    out_b <= in_b[sel]

このために、LowerTypesを見直している。つまり、現時点で何が問題なのかというとLowerTypesの段階でin[sel].Ain_A[sel]に変換で来ているのは良いのだが、この時に間違えて実はin_A[sel]という1つの変数として変換してしまっており、結果的に次のResolveKindsのパスでin_A[sel]という変数は無いよと怒られてしまっている。したがって、in_Aselは別々になるように管理しておかなければならないのだ。

で、なぜin_A[sel]が1つの変数として登録されてしまったかというと、LowerTypesの中で改造をさぼって、

      case (_:SubField, _: WSubIndex) => kind(e) match {
        case InstanceKind =>
          val (root, tail) = splitRef(e)
          val name = loweredTypeName(tail)
          WSubField(root, name, e.tpe, flow(e))
        case MemKind =>
          val exps = lowerTypesMemExp(memDataTypeMap, info, mname)(e)
          exps.size match {
            case 1 => exps.head
            case _ => error("Error! lowerTypesExp called on MemKind " +
                "SubField that needs to be expanded!")(info, mname)
          }
        case _ => WRef(loweredTypeName(e), e.tpe, kind(e), flow(e))
      }

となっており、インスタンスでもメモリでもない場合は単純なWRefによってそれ以降の変数階層をすべて潰してしまい、1つの変数にしてしまっているからである。したがって、in[sel].Aのように、

  • SubField(構造体のメンバへのアクセス)が発生すると、とりあえず構造体のメンバ変数の解析と、構造体の名前の解析に進む。
  • 構造体の名前を解析した結果それが配列であれば(in[sel]のような形)、その配列名を書き直してin_Aという名前に変換してしまう。

という処理が必要になる。このために、上記の処理を下記のように書き直した。

      case e: WSubField => {
        kind(e) match {
          case InstanceKind =>
            val (root, tail) = splitRef(e)
            val name = loweredTypeName(tail)
            WSubField(root, name, e.tpe, flow(e))
          case MemKind =>
            val exps = lowerTypesMemExp(memDataTypeMap, info, mname)(e)
            exps.size match {
              case 1 => exps.head
              case _ => error("Error! lowerTypesExp called on MemKind " +
                  "SubField that needs to be expanded!")(info, mname)
            }
          case _ => {
            val exps = lowerTypesExp(memDataTypeMap, info, mname)(e.expr)
            exps match {
              case ex: WSubAccess => WSubAccess(WRef(loweredName(ex.expr) + delim + e.name, e.tpe, kind(e), flow(e)), ex.index, ex.tpe, flow(ex))
              case _ => WRef(loweredTypeName(e), e.tpe, kind(e), flow(e))
            }
          }
        }
      }

これはWSubFieldWSubIndexを分解して、WSubFieldの型にのみ適用している。InstanceKindでもMemKindでもない場合、

  • lowerTypesExpで構造体な名前の部分(つまりin[sel]の部分)を解析する。
  • 構造体の名前を解析した結果それが配列であれば(つまりWSubAccessであれば)、WSubAccess型を再度作り直す。このときに名前をloweredName(ex.expr) + delim + e.nameとする。つまり構造体の名前とメンバ変数をdelimでくっつけてそれを新しい配列の名前としてしまう。

このようにすることで、上記のテストケースは最終的に以下のように変換されるようになった。

circuit VecBundle :
  module VecBundle :
    input in_a : UInt<32>[4]
    input in_b : UInt<32>[4]
    input sel : UInt<2>
    output out_a : UInt<32>
    output out_b : UInt<32>

    out_a <= in_a[sel]
    out_b <= in_b[sel]

いいね、想定通りだ。

FIRRTLに入門する (16. LowerType時に配列を分解せずにBundleを分解する手法の調査(2))

https://raw.githubusercontent.com/freechipsproject/firrtl/master/doc/images/firrtl_logo.svg?sanitize=true

LowerTypeの調査を続けている、前回の記事からずいぶんと時間が経ってしまったが、Bundleを分解する方法について調査している。つまり、FIRRTLで以下のように記述されているコードを、Vectorを分解せずにBundleのみ分解するということである。

circuit VecBundle :
  module VecBundle :
    input in: { a : UInt<32>, b : UInt<32> }[4]
    input sel: UInt<2>
    output out : { a : UInt<32>, b : UInt<32> }

    out.a <= in[sel].a
    out.b <= in[sel].b

上記のFIRのコードを分解する。下記のようになれば良い。

circuit VecBundle :
  module VecBundle :
    input in_a : UInt<32>[4]
    input in_b : UInt<32>[4]
    input sel : UInt<2>
    output out_a : UInt<32>
    output out_b : UInt<32>

    skip
    out_a <= in_a[sel]
    out_b <= in_b[sel]

このためにLowerType内のメソッドにおいて、loweredNameにおいて、

  • VectorTypeの場合、VectorType自体は維持したまま内部の型を変換する。
  • BundleTypeの場合、Bundleの内部の値を取り出して_でつなげる。
  • WRefの場合、値をそのまま連結して返す。

という手順を取ることにする。これを実現するために、以下のloweredTypeName()を実装した。

  val delim = "_"
  def loweredTypeName(e: Expression): String = loweredTypeName(e, "")
  def loweredTypeName(e: Expression, substr: String): String = e match {
    case e: WRef => {
      if (substr == "") { e.name }
      else              { e.name + "_" + substr }
    }
    case e: WSubField => s"${loweredTypeName(e.expr, substr + e.name)}"
    case e: WSubIndex => s"${loweredTypeName(e.expr, substr)}$delim${e.value}"
    case e: WSubAccess => s"${loweredTypeName(e.expr)}_${substr}[${loweredTypeName(e.index)}]"
  }

これにより、上記のサンプルコードを処理してみる。

sbt assembly && ./utils/bin/firrtl -td regress -i ./regress/VecBundle.fir -X sverilog -ll trace 2>&1 | tee VecBundle.log

まだエラーが出るが、LowerTypeの段階で以下のようになった。

circuit VecBundle :
  module VecBundle :
    input in_a : UInt<32>[4]
    input in_b : UInt<32>[4]
    input sel : UInt<2>
    output out_a : UInt<32>
    output out_b : UInt<32>

    skip
    out_a <= in_a[sel]
    out_b <= in_b[sel]

とりあえず変換はできるようになった。問題は、in_a[sel]が正しく変数に分解できていないということだ。これを処理するためにまだResolveKindsでエラーが発生している。

Exception in thread "main" firrtl.FirrtlInternalException: Internal Error! Please file an issue at https://github.com/ucb-bar/firrtl/issues
        at firrtl.Utils$.error(Utils.scala:423)
        at firrtl.Utils$.throwInternalError(Utils.scala:164)
        at firrtl.stage.FirrtlStage.run(FirrtlStage.scala:35)
        at firrtl.options.Stage$$anon$1.transform(Stage.scala:43)
        at firrtl.options.Stage$$anon$1.transform(Stage.scala:43)
...
        at firrtl.stage.FirrtlMain.main(FirrtlStage.scala)
Caused by: java.util.NoSuchElementException: key not found: in_a[sel]
        at scala.collection.MapLike.default(MapLike.scala:235)
...

DSLでビルドツールを自作する (25日目 最後のまとめ)

この記事は「Qiita Advent Calendar 2019 DSLで自作ビルドツールを作ろう」の25日目の記事です。

25日目 最後のまとめ

ここまで、24回に分けて一からビルドツールの開発に挑戦してきました。

  • DSLの勉強のために、Rubyメタプログラミングについて一から勉強しました。
  • ビルドシステムの勉強のために、ビルドツールの仕組みについて一から調査しました。
  • 様々な機能をビルドシステムに詰め込むために、Ruby/Git/YAMLなどの機能について調査しました。
  • 最終的な目標としてEmacsをビルドするために、Emacsのビルド構成について勉強しました。
    • また、Makefileを自作ビルドシステムに移植するために、Makefileについて詳細な記法を勉強しました。

このプロジェクトは、思いついてからコードを書き始め、24日分の原稿を書くのにコーディングと文章化で約一か月かけました。 これを、私の中で「30日プロジェクト」と呼んでいました。コーディングから文書化まで、すべて30日で納めています。

  • 24回に収めるために、最終目標を定めて大まかにプロジェクトの進捗を分割していく。
  • とりあえず最初から作り始めて、大体最初のモノになったくらいで具体的な24回分の章の分割を考えていく。
    • 大体5回まででビルドツールとしての基本的な機能が実現できたので、そこからはどのような機能が欲しいか、最終目標が何なのか、24回までに分割してスケジュールを組みました。
  • 途中から、明らかにネタが足りなくなったので、YAMLを導入してみたり、gitとの組み合わせを考えてみたり、いろいろ余計なものも付けました。

24回分のコーディングと文章を短期間に書き上げるために以下の方針を取りました。 短期間にコーディングと文章を組み上げるために、私が良く使っている手です。

  • エディタに24回分のサブタイトルを先に書き上げる。
  • 各回のコーディングを終えるたびに、その日のうちに文書化しておく。
  • とにかく最終的な目標を決めておく。今回の場合は、Emacsを自作ツールでビルドする、ということだったので、それに向けて必要な機能とスケジュールを逆算。

という訳で、無事に目標を達成することができました。これくらいの目標であれば、30日でしっかりまとめ上げて文書化までできる、という自分の大きな自信にすることができたと思います。

これからも、新しいものを恐れず、積極果敢に勉強して取り組んでいきたいと思います。

DSLでビルドツールを自作する (24日目 EmacsプロジェクトをRumyでコンパイルしてみる試行 ~ひたすらルールファイルを書く~)

この記事は「Qiita Advent Calendar 2019 DSLで自作ビルドツールを作ろう」の24日目の記事です。

24日目 EmacsプロジェクトをRumyでコンパイルしてみる試行 ~ひたすらルールファイルを書く~

前回に引き続き、ひたすらMakefileをRumyファイルに移していきます。deps.mkファイルは各ファイルのオブジェクトに対する依存関係を記述しているので、これをひたすらコピーしていきます。

  • build.rb
make_cpp_target "atimer.o", ["atimer.c", "atimer.h", "syssignal.h", "systime.h", "lisp.h", "blockinput.h",
                             "globals.h", "../lib/unistd.h", "msdos.h", config_h]
make_cpp_target "bidi.o", ["bidi.c", "buffer.h", "character.h", "dispextern.h", "msdos.h", "lisp.h",
                           "globals.h", config_h]
...

依存関係を記述していくのが面倒なので、make_cpp_targetというラッパーを用意しました。

def make_cpp_target(target, depend_list)
  make_target target do
    executes ["#{CC} #{ALL_CFLAGS} -c #{name.sub(".o", ".c")}"]
    depends [depend_list, "globals.h"]
  end
end

いちいちexecuteのコマンドを書くのが面倒くさいので、もう専用のmake_cpp_targetとう関数を用意してしまったわけです。これでひたすらオブジェクトファイルの依存関係を記述していきます。

依存関係についてもひたすら外部Makeルールを記述していきます。このあたりはRubyで柔軟性があるので、自由に依存関係を記述していきます。

make_target "globals.h" do
  depends ["gl-stamp"]
end

make_target "gl-stamp" do
  depends [libsrc + "/make-docfile" + EXEEXT, GLOBAL_SOURCES]
  executes ["#{libsrc}/make-docfile -d #{srcdir} -g #{obj.join(' ').to_s} > globals.tmp"]
  executes ["#{top_srcdir}/build-aux/move-if-change globals.tmp globals.h"]
  executes ["echo timestamp > #{name}"]
end

さて、ひたすら長いRumyルールファイルを記述したので、さっそくビルドしてみます。いろいろ苦労したのですが、無事にビルドしてemacsをビルドすることができました。どうにかこうにか、Makeと同じことができるようになったわけです。

rumy
...
gcc -Demacs  -I. -I. -I../lib -I../lib   -I/usr/include/uuid -I/usr/include/freetype2 -I/usr/include/libpng16     -I/usr/include/libpng16          -I/usr/include/freetype2 -I/usr/includ
e/libpng16 -I/usr/include/uuid -I/usr/include/freetype2 -I/usr/include/libpng16     -I/usr/include/p11-kit-1   -g3 -O2 -c ftfont.c
gcc -Demacs  -I. -I. -I../lib -I../lib   -I/usr/include/uuid -I/usr/include/freetype2 -I/usr/include/libpng16     -I/usr/include/libpng16          -I/usr/include/freetype2 -I/usr/includ
e/libpng16 -I/usr/include/uuid -I/usr/include/freetype2 -I/usr/include/libpng16     -I/usr/include/p11-kit-1   -g3 -O2 -c xftfont.c
[DEBUG] : ==== Execute Target gl-stamp ====
[DEBUG] : ==== Execute Target gl-stamp ====
Target Execution Skipped
[DEBUG] : ==== Execute Target gl-stamp ====
[DEBUG] : ==== Execute Target globals.h ====
...

ビルドが完了しました!

./src/emacs
f:id:msyksphinz:20191121220316p:plain
RumyでビルドしたEmacsが起動した様子。