プログラムdeタマゴ

nodamushiの著作物は、文章、画像、プログラムにかかわらず全てUnlicenseです

Chisel入門3~メリットデメリット~

f:id:nodamushi:20180203135601p:plain


 さて、入門1ではメリットだけを書きましたが、基本的に私はメリットだけ推して、デメリットを書かない論調が大っ嫌いです。デメリットがない文章はその時点で信用しません。

 というわけで、今回はメリットデメリットを紹介しようと思います。

 

 私が使ってみた上で、実感として感じるメリットは以下です。

  • IntelliJ IDEAといった統合開発環境の強力な補完機能が使える
  • Input,Outputの接続<>が強力
  • 接続を後から書ける
  • Wire、Regが本当に配線とレジスタという物理的な意味を持つ
  • Wire、Regといった要素をプログラマブルに生成出来る
  • 継承を利用することでOOPライクな思考が出来る

 

 逆に私が使った見た上で、これ駄目だろって感じたデメリットは以下です。

  • 新しい言語とパラダイムを学ばなければならない
  • 非同期リセットがない (※Chisel3では実装予定とはなっている) 
  • IOポートをVerilogに変換した時の命名を指定出来ない
  • エラーが超不親切
  • MSYSでVerilatorシミュレーションが出来ない

 
 

 では、まずはメリットから述べていきましょう。 

メリット

統合開発環境の強力な補完機能が使える

 Verilogを書く際の強力なエディタってなんなんでしょうか?私は正直よく分かりません。
 一応Emacs+Companyが私の基本開発環境ですが、Verilogはあんまり良い感じに補完してくれないですね。Visual Studio Codeもまぁ、頑張ってるけど、こう、C++とかJavaとかのように、これって感じじゃない。

 一方、Chiselは結局ただのScalaなので、IntelliJ IDEAやEclipseのScalaプラグインなどを入れれば統合開発環境の恩恵を受けることが出来ます。
 Chiselで久しぶりにScalaを触ったのですが、実用に十分な力があると言って良いです。Scalaが出たばっかの頃の使えないIDEのイメージが強くて、Scala(のIDE)なんて使えねーと想っていたのですが、今回Scala(のIDE)の評価を上方修正しました。

 

 わざわざ「Scala(のIDE)の評価」と、(のIDE)を薄く書いていますが、私は言語の評価と統合開発環境の評価はほぼ直結すると思っています。無論、ライブラリや、フレームワークなど、評価の全てではありませんが。

 そういう意味で、Chiselは良い選択肢でしょう。

 

接続記述が強力、抽象的に書ける

 <>については、既にChisel入門1であげたので、具体的に説明はしませんが、モジュールAのインスタンスaとモジュールBのインスタンスbの入力と出力を単純に繋ぎたい場合、以下の一文で済みます。

a.io <> b.io

 実際には、こんなに単純な接続なんてありませんから、もうちょっと構造を工夫するなどの考察は必要ですが、Chiselの接続は最後に書かれた物が有効などのルールも併用すれば、かなり短く書くことが出来ます。これはかなりメリットですよね。

 もう一つのメリットは、モジュールのインスタンスを宣言した後で、接続記述を書ける、と言う点をあげておきたいと思います。すなわち、インスタンス化記述と接続記述が分離されていることがメリットです。

val a = Module(new AModule)
val b = Module(new BModule)

a.io.INPUT := b.io.OUTPUT

 これにより以下のメリットが得られます。

  • a.io.INPUT等の様に書くことでどのインスタンスの処理をしているかが各行で明確である
  • 分離されたことにより、接続は接続でプログラマブルに処理出来る

 各行で明確だと、後から読む側としては、非常にありがたいですよね。百個ぐらいポートがあることも割とあるので、後から読みやすくなります。

 また、接続を分離して、プログラマブルに書けると言うことは、記述量の圧縮に繋がります。例えば、Module A,B,Cのインスタンスの接続を統一的に扱いたい場合を考えてみましょう。

 Verilogならこのようになるでしょう。

module D( ポート定義 );
  A a(ポート接続);
  B b(ポート接続);
  C c(ポート接続);
endmodule;

 上記のモジュールDを用意した後、必要な場所でDを実体化します。

D d1(ポート接続);
D d2(ポート接続);

 

 一方、Chiselだと以下の様になるでしょう。

val a1 = Module(new A),a2 = Module(new A)
val b1 = Module(new B),b2 = Module(new B)
val c1 = Module(new C),c2 = Module(new C)
//接続処理
def d(a :A,b :B,c:C) =>{
   //a,b,cの接続を書く
}
d(a1,b1,c1)
d(a2,b2,c2)

 

 大して変わらない様に見えますが、Verilogだと、Dという本来不必要なモジュールの階層が必要になります。後からa1の出力が他で必要だったとかなった場合、Dを修正する必要があり、あー面倒くさい。Chiselの方は必要ないですよね。
 なお、階層分けすることが悪い訳ではないので、やっぱりVerilogの方がいい、と言われるかも知れません。ここでは、あくまでmoduleを作らなくても、同じ接続を省略出来るという点に着目してください。

 さらに、RegやModule、Wireなどもプログラマブルに生成出来るので、配列やら再帰関数を使えば、複雑な物も割とさっくり書けます。Verilogだと、あれつないでこれ繋いで、あーーーとかなってるところです。

 継承によるOOPが使えるのもプログラム畑の人間からしたらメリットですね。

 

 

 ただし、これらは人によってはデメリットと捉えることには注意しておきましょう。なぜなら、Verilogの方が具体的だからです。

 例えば、以下のVerilogを考えてみます。

wire boutput;
AModule a (
  .INPUT(boutput),
  //他略
  );
BModule b(
  .OUTPUT(boutput),
  //他略
  );

 AModuleのaのINPUTは常にboutputに繋がります。そして、BModuleのbのOUTPUTも常にboutputに繋がります。a.io.INPUTが一体何処に繋がっているのか具体的に記述されていない、という現象は起こりえません。

 某staticおじさんよろしく、プログラミングの世界では抽象度の低いことは良いこととはあまり見なされませんが、ハードの世界は違います。何故なら、ハードは最終的に具体的な物(回路)になり、その具体的な物に対して検証をせねばならないからです。
 従って、抽象的であることは悪である、と言われた時、それを否定するだけの理屈を私は持っていません。同じ記述の素子であっても、性能ばらつくんだから。

 
 え、ソフトだったら?抽象化が絶対の正義で真理です。コピペマンはコテンパンにしてやんよ、かかってこい。



 

デメリット

 さて、メリット紹介が終わったら次はデメリットですね。がっつり行きましょう。
 
 

新しい言語とパラダイムを学ばなければならない

 Chisel入門1では、あたかもVerilogと一対一対応で書ける様な雰囲気で書きましたが、やっぱり別言語です。効率の良い書き方などを求める場合、どうしても学習からは逃げられません。
 (一応、inline verilogという逃げ道もあるけど)


 どんな言語や機能でもそうですが、これが最大の欠点ですよね。
 Verilogなんて何も変化してませんもんね。OVL対応とか、そういうツールの変化はあったかも知れないけど。え?System Verilog?さぁ………。

 どんな開発現場でも、新技術は導入したくないはずです。その新技術を使うことによるメリット、コストの評価も、信頼性の評価もしなければならないし、新技術なんてあっても良いことないです。技術なんて、少なくとも数百年ぐらいは同じ技術だけで食ってけるのが一番。イノベーションなんて打ち壊せー。ビバ、ラッダイト運動。


 学習を覚悟しなければならない、それはやっぱり一番大きなデメリットだと思います。

 

 

非同期リセットがない(実装予定)

 ちょーっとこれは論外なんじゃないですかねぇ。
 これはたぶんだけど、ChiselがメインターゲットとしてるのがFPGAだからっぽい雰囲気。

 一応、Chisel3.2で実装予定とのことだけど、元々Chisel3.0で実装予定が3.1になり、3.2に、3.3に3.4にとずるずる後ろに下がっていきそう。(追記:めでたく3.3に降格されました!) 元々非同期リセットいらないと言ってた人達らしいので、やる気が低いのでしょうね………。ただ、実装するとは約束しているようです。

 一応、回避策はあって、BlackBoxを使うという手段もあるけど、BlackBoxを使うと今度はFIRRTLのシミュレーションが出来ないでしょうね。


 なお、非同期リセットはないのに、マルチクロックドメインには対応しているという不思議。


 ちなみに、非同期リセットの日本語記事がありました。この記事で最後の方でSpinal HDLというChiselインスパイア言語を知ったのでそっちもちらっと見てみました。

 サクッと読んだ感じ、Spnial HDLは良さそうな感じですが、やっぱ金がね。Chiselは金が動いてる感あって安心感あるもんね。

 ただ、JavaScriptを見てる限り、Alt系の言語は激動の歴史を辿る可能性があります。Chiselにオールインするよりも、色々見ておく方がいいとは思う。

 

IOポートをVerilogに変換した時の命名を指定出来ない

 全てがChiselで終わるなら、別にVerilogに変換した時にどういう命名になろうが、そこまで気にしないでいいです。
 でも、全てがChiselで済む訳じゃないですよね。ChiselからVerilogを呼び出すのはまだ良いのですが、ChiselをVerilogから呼び出すってことも当然ある訳で。

 例えば、以下の様なモジュールの入力ポートは、「in」という名前にせよ、という指令が来ていても、Chiselでは出来ないのです。

class A extends Module{
  val io =IO(new Bundle{
    val in = Input(new Bool) //これは「input  io_in」というポートになる
  })
}

 いや、一応出来るんですよ。こうすれば。

class A extends Module{
  val io =IO(new Bundle{})
  val in = IO(Input(new Bool)) //これは「input  in」というポートになる
}

 ただ、こうすると、Bundleは使えないし、Chiselのルール的にI/Oなのかよくわからんくなる。(io.~は入出力)

 だから、こんな機能付けて欲しいですよね。

io.in.setName("in")

 というのも、これ、Chisel2ではあったそうなのですが、Chisel3では消されました。
 で、やっぱ不満に思っている人が居るらしく、issueにありました。requestとして受け入れられて、今のところ3.2で実装予定(あくまで予定)のようです。 拒否されました。ラッパー使えと。え、そういう問題なの!?

github.com

 ただ、何をどう実装するつもりなのか、ノープランぽいって言うか、とりあえず全部3.2にしとけって感じなので、ほぼ確実に3.2では実装されないでしょう。 
 
 
 

エラーが超不親切

 ビックリするくらい不親切だと思うわ。C++のtemplateと良い勝負してる気がする。

 C++のエラーメッセージがだいぶ親切になってきたのは近年だから、まだまだ時間はかかりそうかなー。

MSYSでVerilatorシミュレーションが出来ない

 Verilatorは問題ないのですが、Chiselが吐き出すC++コードがUnixのシステム(sys/mmanとか)に依存しています。
 なので、VerilatorシミュレーションはMSYSでやるのは諦めました。うん、めんどうくさい。WSLでUbuntu使えばいいだけだし。

 でも、できればさ、MSYSで完結したかったなぁ。うん、超個人的なデメリットだけど。

 え、Cygwin?………やーだー……


 


 どうでしたでしょうか。
 正直、結構デメリットの非同期リセットが出来ないというのと、ポート名を変えられないという点が結構痛くて、部分的にしかプロジェクトでは使えないと思っています。
 が、逆に私が論外すぎるなぁと思ったのもこの二点ぐらいしかないので、3.2でどのぐらい改善してくるのか、楽しみにしております。


 というわけで、ようやく次回から、Chiselの使い方の説明に入っていこうかな。

Chisel入門2~開発環境の準備~

 やーやーやー。

 ハードウェアをScalaで書ける組み込み言語、Chisel3の入門インストール講座です。

 というわけで、面倒でもまずは開発環境を準備するところから始めよう。



 ごらんの番組は以下の内容でお送りいたします。

  • Chisel開発に必要な物
  • JDKのインストール
    • Windows上で開発する人
    • Linux上で開発する人
  • IntelliJ IDEAをインストールする
    • Windowsの人
    • Linux(Ubuntu)の人
  • IntelliJ IDEAの初期設定(Windows、Linux共通)
    • プロキシ環境下:プロキシの設定
    • プロキシ環境下:プロキシが認証局証明書を上書きする場合
    • プロキシ環境下:Scalaプラグインを入れる
  • IntelliJ IDEAでChiselを動かしてみる
  • おわりに
  • 旧情報(Oracle JDKのインストール)


 
 

Chisel開発に必要な物

 以下の物が揃っていれば、すぐに開発を開始出来ます。

 JDKとか、Javaとか、IntelliJ IDEAとか見たこともない謎の横文字が並んでしまって大変申し訳ない。我慢してインストールに付き合ってください。

 Verilogを開発するOSはLinuxである可能性も高いから、WindowsとLinux(Ubuntu)で解説します。OSが分からない人は、ExcelがあるのがWindowsで、Excelが無いか、なんかパチモンっぽいのが動くのがLinuxです。


JDKのインストール

 JDKというのは、Chiselを動かすのに必要なソフトです。

 
 

Windows上で開発する人


 2019年からOracleのJDKは有償化されます。それに伴い、現在JDKは移行先の混迷期にあります。一応OpenJDKベースで書き直しましたが、信用はしないで下さい。

 無償のJDKとしては、OpenJDK以外にも、Adopt OpenJDKやAmazonのCorrettoがあります。

 

 OpenJDKをJDK Builds from Oracleからダウンロードし、任意の場所に展開して下さい。今回は、JDK11というバージョンのWindows版をC:\bin\jdk-11.0.1に展開しました。

 

 画像は2018年11月現在のキャプチャであり、今現在とは必ず違うので、臨機応変に対応して下さい。

f:id:nodamushi:20181126013942p:plain:w320

 JDK11のリンクを押すと、次の様な画面になります。

f:id:nodamushi:20181126013919p:plain:w320

 Windows用をダウンロード(32bitはありません)し、適当な場所に展開します

f:id:nodamushi:20181126013430p:plain:w320

 

 

Linux上で開発する人

 Linuxの人用です。Windowsの人は飛ばしてください。
 今回はUbuntu仮想環境を使いました。端末で以下を入力してください。CentOSやRed Hat Enterprise Linuxの場合はyumかな。

sudo apt-get install default-jdk

 あー、Windowsと違って楽でいいんじゃー。キャプチャ画像とかいらないよね?

 もし、管理者権限がない場合でも、JDKのダウンロードページからダウンロードし、適当なところに展開してパスを通せば使うことが出来ます。意味が分からない人は管理者に「JDKと、IntelliJ IDEAを入れてください」と連絡するのが早いです。

 

 

IntelliJ IDEAをインストールする

 IntelliJ IDEAはChisel開発もできるエディター(メモ帳)です。正確には統合開発環境と言います。

 Windowsの人も、Linuxの人もIntelliJ IDEAのダウンロードページからCommunity版(無料)をダウンロードしてください。Ultimate版は有料です。

f:id:nodamushi:20180204215731p:plain:w450


Windowsの人

 インストールをする際に、必ずしも画像と同じにならない場合があります。そういうときは焦らずに、画面に何が書いてあるのかを読んで、理解をしてから、手順を判断してください。
 
 インストーラを起動すると、次の様な画面になりますので、「Next」を押してください

f:id:nodamushi:20180204220819p:plain:w250

 
 インストールディレクトリを指定します。デフォルトのままで良いので、「Next」を押してください
f:id:nodamushi:20180204220824p:plain:w250

 
 これもそのままで、「Install」を押してください
f:id:nodamushi:20180204220829p:plain:w250

 
 暫く待機します。
f:id:nodamushi:20180204220837p:plain:w250

 
 お疲れ様です。インストール完了です。後述の「IntelliJ IDEAの初期設定(Windows、Linux共通)」へ進んでください。
f:id:nodamushi:20180204220845p:plain:w250

 
 

Linux(Ubuntu)の人

 ダウンロードしたファイルを適当なディレクトリに展開します。
 展開したら、展開したフォルダ/binにPATHを通します。
 パスを通したら端末を起動して以下をタイプ。

idea

 あー、楽なんじゃー。

 
 

 
 

IntelliJ IDEAの初期設定(Windows、Linux共通)

 IntelliJ IDEAでChiselが開発できるように初期設定を行います。

 初回起動時は以下の様な質問が聞かれます。(画像はLinux版)設定のインポートはすることないので、「Do not import settings」のままOKを押します。

f:id:nodamushi:20180204222051p:plain:w250

 
 ライセンスに同意する画面に映るので、全ての条項を読んで、Acceptを押してください。(一番下まで読まないとAcceptは押せません)
f:id:nodamushi:20180204222054p:plain:w250


 
 見た目をどうするかを聞かれるので、好きな様に選択してください。私は黒にしました。
f:id:nodamushi:20180204222100p:plain:w250



 ※Linuxのみ デスクトップエントリーを作るかどうか聞かれるので好きにしてください。
f:id:nodamushi:20180204222450p:plain:w250


 
 何を有効にするか聞かれます。そのままでも良いですし、画像の様に、Chisel開発に必要ない「Bild Tool」「Swing」「Android」「Plugin Development」はDisableにしても良いです。
f:id:nodamushi:20180204222640p:plain:w250


 
 重要 ScalaをInstallしてください。

※プロキシを設定してないと出ません。後からPluginでScalaを入れることも可能です。
f:id:nodamushi:20180204222813p:plain:w250

 

 以上で初期設定はお終いです。起動画面が立ち上がります。
f:id:nodamushi:20180204223004p:plain:w250

 
 
 プロキシが必要ない、もしくは、プロキシ設定しなくてもScalaがインストールできた人は、次のプロキシ環境下:●●の節は飛ばして下さい。

 プロキシ設定が必要な人は、次の説も読んでください。 

プロキシ環境下:プロキシの設定

 会社など、プロキシを通さないといけない環境の人は、プロキシ設定をしてください。プロキシ設定はConfigure→Settingsから開けます。

f:id:nodamushi:20180204233043p:plain:w250

 
1. IntelliJ IDEAのプロキシ設定。ご自分の環境に合わせて選択してください。
f:id:nodamushi:20180204233137p:plain

 
2. Scalaプラグインがをインストール済み:sbtのプロキシ設定。VM parametersにご自身の環境に合わせて以下を入力してください。まだScalaを入れていない場合は、入れてから設定します。(入れ方は後述するので、慌てないでください)
※URLとPortは自分の環境に変えてください

-Dhttp.proxyHost=URL
-Dhttp.poxyPort=Port
-Dhttps.proxyHost=URL
-Dhttps.proxyPort=Port

ユーザー名、パスワードが必要な場合は、-Dhttp.proxyUserと-Dhttp.proxyPassword、-Dhttps.proxyUser、-Dhttps.proxyPasswordも追加。
f:id:nodamushi:20180204233313p:plain

 
 

プロキシ環境下:プロキシが認証局証明書を上書きする場合

 プロキシがHTTPSなどの証明書を勝手に妙な証明書に変えてる場合なんかは、かなり面倒です。糞ニートを名乗ってるのに、まるで体験した様な書き方ですね。(遠い目
 こういう環境かどうかはFirefoxを入れてみて、Googleが開けるか確認してみてください。開けなければ妙な証明書に書き換えています。
 こういう場合は、JDKと、IntelliJの中にあるJREにkeytoolを使って認証局証明書を登録するという作業をする必要があります。
 が、上↑の文章意味分からないですよね。大丈夫です、情強を名乗ってる人でもほとんど分かりません

 分かる部署に押しつけましょう。


 
 

プロキシ環境下:Scalaプラグインを入れる

 プロキシ設定をしてなかったせいで、最初にScalaを入れられなかった場合は、Scalaプラグインを入れてください。プロキシ設定の時と同じく、Configure→Settingsで設定画面を開きます。
f:id:nodamushi:20180204233043p:plain:w250

 
 Pluginの項目を開いたらBrowse repositories...をクリックしてください。
f:id:nodamushi:20180204235047p:plain:w400



 Scalaというプラグインを探し、Installをしてください。検索ボックスにScalaと打ち込むと探しやすくなります。
f:id:nodamushi:20180204235158p:plain:w400
 

 入れたら、sbtのプロキシを設定します。VM parametersにご自身の環境に合わせて以下を入力してください。(再掲)
※URLとPortは自分の環境に変えてください
ユーザー名、パスワードが必要な場合は、-Dhttp.proxyUserと-Dhttp.proxyPassword、-Dhttps.proxyUser、-Dhttps.proxyPasswordも追加。

-Dhttp.proxyHost=URL
-Dhttp.poxyPort=Port
-Dhttps.proxyHost=URL
-Dhttps.proxyPort=Port

f:id:nodamushi:20180204233313p:plain
 

IntelliJ IDEAでChiselを動かしてみる

 IDEAが起動すると、以下の様な画面になります。とりあえず、「Create New Project」をクリックしてください。
f:id:nodamushi:20180204223146p:plain:w250

 
 Scala→sbtと選択し、「Next」ボタンを押してください
f:id:nodamushi:20180204223319p:plain:w400


 
 Nameにプロジェクト名を適当に入れてください。保存場所が気に入らなければLocationを適当に変更してください。Windowsの人はもう一つすることがあるので、まだFinishを押さないでください。Linuxの人はFinishを押してください。
 f:id:nodamushi:20180204223408p:plain:w400


 
 ※Windowsの人のみ
 Windowsの場合、JDKが自動的に入力されません。なので、自分で最初にインストールしたJDKを教える必要があります。
f:id:nodamushi:20180204223542p:plain:w400


 
 ※Windowsの人のみ
 JDKの横にあるNewボタンを押すと、ポップアップが開くので、インストールしたJDKを選択してください。その後、Finishを押すと、プロジェクトが作成されます。

f:id:nodamushi:20181126020408p:plain:w400

 
 
 
 しばらくすると以下の様な画面になります。初回はかなり時間かかりますから、コーヒーでも飲んでてください。
f:id:nodamushi:20180204223800p:plain:w250


 
 下でクルクル回ってた奴が、こんな感じになればOKです。
f:id:nodamushi:20180204223944p:plain:w400


 
 左上にあるProjectの中から、build.sbtを探してダブルクリックします。
f:id:nodamushi:20180204224056p:plain

 
 
 build.sbtに以下の内容を追加してください。この設定を書くことで、このプロジェクトでChisel3が使える様になります。なお、この記事はChisel3.0の時代の記事なので、3.1とか3.2とか、4.0とかが出ていたら、libraryDependenciesを必要に合わせて変更してください。GitHub - freechipsproject/chisel3: Chisel 3で確認できます。

scalacOptions ++= Seq("-deprecation", "-feature", "-unchecked", "-language:reflectiveCalls")
val chiselGroupId = "edu.berkeley.cs"
libraryDependencies ++= Seq(
  chiselGroupId %% "chisel3" % "3.0.+",
  chiselGroupId %% "chisel-iotesters" % "1.1.+"
)
resolvers ++= Seq(
  Resolver.sonatypeRepo("snapshots"),
  Resolver.sonatypeRepo("releases")
)

f:id:nodamushi:20180204224620p:plain:w400


 
 ファイルを保存すると、Linuxだったら上の方に、Windowsだったら下の方(?あやふや)に「Use auto-import」がうんうん、とかいう質問が出てくると思う(ごめん、キャプチャ忘れてた)ので、「Use auto-import」を有効化してください
 出てこない、よく分からん、私の様に見逃したという人は、メニューバーのFile→Settingsをクリックして設定画面を開き、sbtの項目を開いて、「Use auto-import」にチェックを入れてください。
f:id:nodamushi:20180204225049p:plain

 

 
 先ほどクルクル回ってた奴の横にある、リロードみたいなボタンを押します。 再び処理にかなり時間がかかるので、お茶でも飲んで待っててください。
f:id:nodamushi:20180204224821p:plain

 
 
 処理が完了すると、External Library(外部ライブラリ)の項目がずらっと増えます。これで、プロジェクト内でChisel3が使える様になりました。
f:id:nodamushi:20180204224950p:plain:w250

 

 プロジェクトのsrc/main/scalaフォルダを右クリックし、ポップアップメニューの中からNew→Scala Classを選択します。
 f:id:nodamushi:20180204225610p:plain:w400

 
 
 Helloと入力し、OKをクリックします。
f:id:nodamushi:20180204225829p:plain

 
 
 Hello.scalaが作成されてエディタが開くので、以下の内容を打ち込んでください。前回脳汁飛ばしながら紹介したコードです

import chisel3._
import chisel3.core.UserModule

class MyPort extends Bundle{
  val A =  Input(Bool())
  val B =  Input(Bool())
  val C = Output(Bool())
  val D =  Input(Bool())
}
class Hello extends UserModule{
  val S = IO(new MyPort)
  val M = IO(Flipped(new MyPort))
  S<>M
  M.D := S.A | S.D
}
object Hello extends App{
  chisel3.Driver.execute(args,()=>new Hello)
}

f:id:nodamushi:20180204230226p:plain:w400

 
 
 次に、sbt shellを開きます。sbt shellという文字が画面内に見つからない場合は、画面左下の四角いボタンを押して、sbt shellを選択してください。
f:id:nodamushi:20180204230547p:plain

 

 
 sbt shellが暫く立ち上がるのに時間がかかります。動きが止まって [IJ]sbt:プロジェクト名> という文字が出たら立ち上がり完了です。
 立ち上がりを確認したら、Chiselを動かしてみましょう!
 「run Hello」と打ち込んで、Enterを押してください。
f:id:nodamushi:20180204230818p:plain

 

 
 問題が起こらなければ、以下の図の様に [success ] の文字が出てきます。
f:id:nodamushi:20180204231025p:plain

 

 
 では、実際に生成されたVerilogファイルを見てみましょう。
 プロジェクトを右クリックして出てくるポップアップメニューから、Windowsなら「Show in Explorer」を、Linuxなら「Show in Files」を選択してください。
f:id:nodamushi:20180204231322p:plain:w250

 
 
 すると、プロジェクト内にHello.vというファイルが生成されているはずです。できてますよね!?おめでとうございます!
f:id:nodamushi:20180204231424p:plain:w250

 
 実際に中身を開いてみるとこのような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 Hello( // @[:@3.2]
  input   S_A, // @[:@4.4]
  input   S_B, // @[:@4.4]
  output  S_C, // @[:@4.4]
  input   S_D, // @[:@4.4]
  output  M_A, // @[:@5.4]
  output  M_B, // @[:@5.4]
  input   M_C, // @[:@5.4]
  output  M_D // @[:@5.4]
);
  wire  _T_13; // @[Hello.scala 14:14:@11.4]
  assign _T_13 = S_A | S_D; // @[Hello.scala 14:14:@11.4]
  assign S_C = M_C;
  assign M_A = S_A;
  assign M_B = S_B;
  assign M_D = _T_13;
endmodule

 

おわりに

 いかがでしたでしょうか。
 JDKとか言う謎の物と、IntelliJをインストールさえすれば、開発開始出来るので意外と簡単だったのではないでしょうか?

 まぁ、この記事書いた私はすっげー疲れましたが。

 Chisel開発の最初のインストールの段階で躓く、ということが、この記事で解消出来たなら幸いです。
 

続きを読む

Chisel入門1~今すぐVerilogを捨てるべき理由~

 日本人だけが知らないScala製のChisel!とか、煽り文章とか考えてみたけど、タイトルだけでギブアップ。別にVerilogもVHDLも捨てなくて良いです。


 今回はChiselというハードウェアを記述するScalaのライブラリ(埋め込み言語)をご紹介します。
 なお、Scalaって単語に釣られたけど、ハードとか知らんし、結論だけで回れ右したい人は、「AltJSの回路版」で全てを理解できるでしょう。お疲れ様でした。


 さて、今世界でブームになりつつある(なってる?)様ですが、例によって日本はいつも通りあまり知名度が無さそうです。そんなChiselを勉強してみて、これスゲーとなったので、まずは何故Chiselを導入すべきなのか、という記事を書いてみたいと思います。



 

そもそもChiselって何?

f:id:nodamushi:20180203135601p:plain
 ChiselはUC Berkeleyが開発している、Scalaというプログラム言語に組み込まれた、簡単に言えばVerilogやVHDLを生成するライブラリです。Chiselの意味は彫刻刀で、読み方はチィーゼルって感じでしょうか。
 身近な例で言えば、topの接続の為に、Verilogを何かの表から自動生成したことはないでしょうか?とーっても雑に言えば、あれです。
 繋ぐだけじゃなくて論理記述やテストの作成とかも出来ます。

 
 こういうライブラリはJavaやらPythonRubyで作成してる人もいたりで、雨後の筍のように乱立しています。それだけVerilog書くのがしんどいと言うことですね。かくいう私も、自作のLisp(Schme)ライブラリでVerilog吐き出してました。

 そんな中、何故Chiselなのかというと、だいたいこいつの所為です。
f:id:nodamushi:20180203145913p:plain
 今世界中で話題のオープンなISA(命令セットアーキテクチャ)のRISC-V。メンバ企業にはGoogleやらIBMやらNVIDIAやらTI,Mentor、MicrosemiにWestern Digital、NXPやらと、もう巨人の集まりです。米やインドは国単位の動きすらもあるようで。脱ARMや脱Intelの動きですかね。
 で、このRISC-V実装のRocketChiselで書かれています。(coreの実装を読むのはしんどいので、AMBAあたりが読むの楽。)RocketはUC Berkeleyで作ってる、RISC-Vの大本みたいな奴です。Chiselも同じバークレイ。なお、RISC-Vの実装は他にもあります
 巨額の金が動いてるので、Chiselが放置されるとか、時代の流れに取り残される心配が少ないのもメリットの一つでしょう。(世の中所詮金!)

 

 

Chiselを導入するメリット

 信用は金の別名ですから、Chiselが十分に信用あると理解して頂けたかと思います。が、だからといって実際に使うかどうかは別問題ですよね。
 そもそもVerilogとかVHDLを書いてる人が、私の様にC++やらJavaやらSchemeやら使える方がキモイのです。普通は精々Tclぐらいしか書けないでしょう。あぁ、スペース地獄のTclとか滅べば良いのに。
 そんな方々に、Chiselで論理組めと言っても拒絶されて当然です。だいたい、ChiselのベースとなってるScalaで検索すると、オブジェクト指向、関数型、Java、モナド、圏論と聞いたこともない単語が殴りかかってきます。なので、最初は検索しないこと推奨です。

 なので、先ずは浅いところだけ、ね、先っちょだけだから、ちょっとだけだから、いいでしょ?って感じでメリットを紹介したいと思います。

基礎:moduleをChiselで書く

 メリットをお話しする前に、最低限の基本である、モジュールの書き方だけ紹介します。細かい理解はしなくて良いです。
 以下の様な、入力が出力に抜けていくモジュールを作ってみましょう。
f:id:nodamushi:20180203162051p:plain

 Verilogだとこうですね。

module Hello( 
  input   i, 
  output  o 
);
  assign o = i;
endmodule

 これをChiselで書くと、こうなります。

class Hello extends UserModule{
  val i = IO(Input(Bool()))
  val o = IO(Output(Bool()))

  o := i
}

 完全に一対一対応ですね。なんとなく、読めるんじゃないでしょうか。Chiselがちょっと冗長な程度です。
 ね?怖くない。


脳汁垂れる入出力の書き方

 先ほどの例だと、Chiselの方が書くの面倒で、何が良いのか分かりませんね。もうちょっと状況を面倒にしてみましょう。
 先にこの節の結論を言っておくと、SystemVerilogが使えない環境でも、SystemVerilogのInterfaceの様な物が使えると言う話です。

 さて、以下の様に、何かのMasterとSlaveがいて、その間を繋ぐ、バスのようなモジュールを考えてみましょう。
f:id:nodamushi:20180203163412p:plain

 Verilogだと例えば、こんな感じですね。(図と同じではありません。)

module Hello(
  input     S_A,
  input     S_B,
  output    S_C,
  input     S_D,
  output    M_A,
  output    M_B,
  input     M_C,
  output    M_D
);
endmodule

 あぁ、面倒くさいんじゃ―
 Chiselではこうなります。

class Hello extends UserModule{
  val S_A = IO( Input(Bool()))
  val S_B = IO( Input(Bool()))
  val S_C = IO(Output(Bool()))
  val S_D = IO( Input(Bool()))
  val M_A = IO(Output(Bool()))
  val M_B = IO(Output(Bool()))
  val M_C = IO( Input(Bool()))
  val M_D = IO(Output(Bool()))
}

 どうですか!?すごいでしょ嬉しいでしょ!?脳汁垂れてきません?え!?嬉しくない!?
 うん、知ってる。何も嬉しくないよね。

 

 このままでは何も嬉しくないので、Bundleという複数の信号を一つのデータとしてまとめる機能を使ってみましょう。以下の様に、入力と出力をまとめることが出来ます。

class Hello extends UserModule{
  val S = IO(new Bundle{
    val A =  Input(Bool())
    val B =  Input(Bool())
    val C = Output(Bool())
    val D =  Input(Bool())
  })
  val M = IO(new Bundle{
    val A = Output(Bool())
    val B = Output(Bool())
    val C =  Input(Bool())
    val D = Output(Bool())
  })
}

 構造化されて、少し見やすくなったでしょうか?
 でも、まだ嬉しくないですよね。

 

 安心してください。更に以下の様に書き換えることが出来ます。

class MyPort extends Bundle{
  val A =  Input(Bool())
  val B =  Input(Bool())
  val C = Output(Bool())
  val D =  Input(Bool())
}
class Hello extends UserModule{
  val S = IO(new MyPort)
  val M = IO(Flipped(new MyPort))
}

 脳汁溢れる!

 どうですか。なんと、マスターポート定義が消えました。
 Chiselでは、Flippedによって入出力を反転したデータを定義することが出来るのです。

 ちなみに、これをVerilogに変換させると、ちゃんと最初のVerilogになります。
 SystemVerilogが使えない環境で、interfaceを利用することが出来るのです。

 

脳汁ブシャーなつなぎ方

 先ほどの例を更に発展させ、マスターポートとスレーブポートを直結しましょう。Verilogならこうなりますね。

module Hello(
  input   S_A, 
  input   S_B, 
  output  S_C,
  input   S_D, 
  output  M_A,
  output  M_B,
  input   M_C,
  output  M_D
);
  assign S_C = M_C;
  assign M_A = S_A;
  assign M_B = S_B;
  assign M_D = S_D;
endmodule

 ま、まぁ、まだ4つしかないから何とかなりますね。しんどいけど。
 じゃぁ、Chiselで書いてみましょう。

class MyPort extends Bundle{
  val A =  Input(Bool())
  val B =  Input(Bool())
  val C = Output(Bool())
  val D =  Input(Bool())
}
class Hello extends UserModule{
  val S = IO(new MyPort)
  val M = IO(Flipped(new MyPort))
  S<>M
}

 脳汁ブシャーーー!

 

 いやー、うん、素晴らしい。まぁ、この位はSystemVerilogのinterfaceでも出来ますが。
 これでも、異なるモジュールのインスタンスの入出力を繋ぐ場合には十分に有用ですが、単一のインスタンスの中で入出力をそのまま直結することはないですよね。次は、ABCは直結するけど、出力Dは入力Aと入力DのORを取ることにしてみましょう。

class MyPort extends Bundle{
  val A =  Input(Bool())
  val B =  Input(Bool())
  val C = Output(Bool())
  val D =  Input(Bool())
}
class Hello extends UserModule{
  val S = IO(new MyPort)
  val M = IO(Flipped(new MyPort))
  S<>M
  M.D := S.A | S.D
}

 脳汁弾けて阿呆になりそう

 実は、S<>Mで一度M.DはS.Dに直結しているのですが、Chiselは最後に書かれた接続が有効になるので、M.D:=S.A | S.Dだけが結果として残ります。他のポートはS<>Mでの接続がそのまま残ります。


 

まとめ

 今回は、文法など、細かいことは気にせず、繋ぐという点だけに絞って紹介してみました。(最後に微妙にORの論理が入りましたが。)

 どうでしたでしょうか?非常に強力なツールだと実感して貰えたでしょうか?今回の内容だけだと、SystemVerilogを使っている人には、あまり響かなかったかも知れませんが。
 論理の実装はVerilogで行い、繋ぐところはChiselでという接着剤的な使い方から初めても良いと思います。

 今熱いChiselを是非初めて見ましょう。

 というわけで、次回から実際の入門記事でも書こうかな。

WindowsでVerilatorをインストール(MSYS2)

 VerilatorとはVerilogコードをシミュレーション可能なC++コードに変換するツールです。Windowsにインストールしてみましょう。

www.veripool.org

 

MSYS2上でインストール

 MSYSを起動して、以下をタイプ。32bit板を入れたい人はmingw-w64-i686-verilator。

pacman -S mingw-w64-x86_64-verilator 

 う~ん、ただ、折角入れるなら、最新バージョン入れたいな。(2018年1/31現在、pacmanのだと、バージョン3.914。最新は3.918。3.918のSupport > 64 bit decimal $display.が気になった。)

 

 というわけで、MSYS2上で野良ビルドしてみましょう。野良ビルド必要ない人は上記のコマンドでOKです。次のシミュレーションしてみた、にどうぞお進みください。

 まずは、字句解析器を作ってくれるflexが必要なので、入っていない場合はflexを入れておきましょう。flexは使ったことないけど、jflexにはよくお世話になっております。

pacman -S flex

 私の場合、足りないのはflexだけでしたが、他に足りない物がある場合は各自で入れてください。

 必要な物をインストールしたら、適当な作業用ディレクトリを作成し、そこに移動してから、以下のコマンドを実行します。何が足りないか分からない場合でも、configureを動かした段階で分かるのでひとまず実行しても良いでしょう。

git clone http://git.veripool.org/git/verilator
cd verilator
# もし特定のバージョンにしたい人は以下の様にしてください。(以下はversion3.904の場合)
# git branch refs/tags/verilator_3_904
export CPPFLAGS=-I/usr/include
autoconf
./configure
make

 /usr/includeの下にあるFlexLexer.hが見つからないとエラーが出るので、環境変数CPPFLAGSでインクルーディレクトリを追加しています。

 

 特に問題なければ、そのままビルド完了するので、make installでインストール。

make install

 ちなみに、make testを実行すると何か、「mkdir」でエラー起こしました。どうもパーミッションを渡そうとして、引数超過となっているらしい。う~ん、大丈夫かな?

 まぁ、この程度なら実際にビルドする時にエラー起きても手動で簡単に直せそうだしいっか。
 
 

適当にシミュレーションしてみる

 とりあえず、シミュレーションをしてみましょう。てきとーに、こんな状態遷移をする謎のステートマシンmoをVerilogで用意し、このステートマシンmoを動かすテストベンチをC++で用意しました。

f:id:nodamushi:20180131235424p:plain


mo.v

module mo
  (
   input        CLK,
   input        RST,
   input        I,
   output [1:0] STATE,
   output [1:0] NEXT_STATE,
   output       DONE
   );

   parameter STATE_START =2'd0;
   parameter STATE_A     =2'd1;
   parameter STATE_B     =2'd2;
   parameter STATE_END   =2'd3;
 
   logic [1:0]  state;
   logic [1:0]  next_state;

   always @(posedge CLK,negedge RST) begin
     if(~RST) 
       state <= STATE_START;
     else 
       state <= next_state;
   end
   

   always @(*) begin
      case (state)
        STATE_START: next_state = I ? STATE_A : STATE_START;
        STATE_A    : next_state = I ? STATE_B : STATE_A;
        STATE_B    : next_state = I ? STATE_A : STATE_END;
        STATE_END  : next_state = STATE_END;
        default : next_state = 2'bxx;
      endcase 
   end

   assign STATE = state;
   assign NEXT_STATE=next_state;
   assign DONE  = state == STATE_END;
   
endmodule          


 

 
tb_top.cpp

#include <iostream>
#include "Vmo.h"
#include "verilated.h"

vluint64_t main_time = 0; //Current simulation time

double sc_time_stamp() { //Called by $time in Verilog
  return main_time;
}
template<typename T>
class Sim{
  T* instance;
public:
  template<typename... ARGS>
  Sim(ARGS... args):instance(new T(args...)){}
  ~Sim(){delete instance;}
  operator vluint64_t()const{return main_time;}
  void operator()(){
    instance->eval();
    main_time++;
  }
  bool isDone(vluint64_t max_sim_time)const{
    return Verilated::gotFinish() || max_sim_time <= main_time;
  }
  T* operator ->()const noexcept{return instance;}
};

int main(int argc, char *argv[])
{
  Verilated::commandArgs(argc,argv);
  Sim<Vmo> sim;
  sim->CLK = 0;
  sim->RST = 0;
  sim->I = 0;
  std::cout << "Simulation Start!"<<sim<<std::endl;
  while(!sim.isDone(100)){
    if(sim == 10){
      std::cout << "reset release" << std::endl;
      sim->RST = 1;
    }
    if((sim % 5) == 0)
      sim->CLK = !sim->CLK;
    if(sim == 10 || sim == 60){
      sim->I = 1;
      std::cout << "sim->I=1"<<std::endl;
    }else if(sim == 40 || sim == 70){
      sim->I = 0;
      std::cout << "sim->I=0"<<std::endl;
    }
    sim();

    printf("Time %d :clk = %d,rst=%d,i=%d, state = %d,next_state = %d, done = %d\n",
           (vluint64_t)sim,sim->CLK,sim->RST,sim->I,sim->STATE,sim->NEXT_STATE,sim->DONE);

  }
  std::cout << "Simulation Done!"<<std::endl;
  sim->final();
  return 0;
}

 Simクラスは、使い回そうと思って作っただけだから、大した意味は無いです。記憶力悪いから、main_timeをインクリメントしたり、Verilated::gotFinish() とかなんて一々覚えてられる訳がないのだ。
 後、Vmoをnewしないといけないのは、理由はよく分からないけど、物によってはスタックに乗らない程度にデカいオブジェクトになることがあるからかな?mo程度の小さい物なら、別にnewしなくても普通に動いた。

 で、これを次の様にしてビルドします。lintが邪魔な場合は、-Wallの代わりに、-Wno-lintを指定してください。

verilator --cc -Wall mo.v --exe tb_top.cpp
make -j -C obj_dir -f Vmo.mk Vmo

 ビルドに成功すると、obj_dirにVmo.exeが出来ているので、それを実行する。

obj_dir/Vmo.exe

 正常に動いて、シミュレーション時間が81の段階でSTATEが3(STATE_END)に移行し、DONEが1になっているのが確認出来ました。

Time 80 :clk = 0,rst=1,i=0, state = 2,next_state = 3, done = 0
Time 81 :clk = 1,rst=1,i=0, state = 3,next_state = 3, done = 1
Time 82 :clk = 1,rst=1,i=0, state = 3,next_state = 3, done = 1


 やったね ( ^ω^)

10進数データ→16bitのIntexHexFileに変換するPythonコード

 10進数のファイルデータを16bitアドレスのIHXに変換するPythonコード。EXEでフリーのがあるけど、exeは困るので、Pythonで書いた。
 今更16bitのIHXファイルなんて、誰が喜ぶんだか。


 以下みたいに一行1つの10進数が並んでいるファイルを喰わせると、標準出力にIHXを吐き出すので、適当に保存してください。

123
1
0
10
25

実行:

python ihx16.py 入力ファイル > rom.ihx


 標準入力からも可。

cat ファイル | python inh16.py > rom.ihx

コード:

Convert decimal data file to intex hex file

MSYSで標準出力をクリップボードにコピー

 MSYS2で標準出力をクリップボードにコピーしたい。要するに「pbcopy」が使いたい。え?WSL?ギリギリまでWindows 7だよ。

 で、「pacman -S pbcopy」ってやっても、「pacman -S xsel」ってやっても、なもんねぇと言われた。マジかー。

 と思ってたんだけど、なんと、Windowsには標準で「C:/Windows/System32/clip.exe」というpbcopyがあったらしい。ということは、これにpbcopyのaliasを貼ればpbcopyが使えるジャマイカ!

 というわけで、~/.bash_profileに以下を追加記述。ちなみに、MSYS2のbashはログイン時に.bashrcを読み込まない設定になっている。

alias pbcopy="/c/Windows/System32/clip.exe"

 

 これで、pbcopyが使える様になった訳なので、早速試してみます。

echo "コピー出来てますか?" | pbcopy

ペースト。↓
繧ウ繝斐・蜃コ譚・縺ヲ縺セ縺吶°・・

 

 ………オーマイガ。そうか、文字コードか。MSYSの文字コードはUTF-8だもんな。Shift_JIS滅びねぇかな。

MSYSの文字コードを変更するべきか否か

 MSYSの文字コードは変更すること可能です。本当に変更するかどうかは、しっかり考察した方がいいですが。

 変更するには、ウィンドウのタイトルバーを右クリックして、ポップアップするメニューからOptionを選択し、以下の様に設定し、save。

f:id:nodamushi:20180112190722p:plain

 これでもう一度先ほどのコマンドを実行して、ペースとしてみると↓
コピー出来てますか?

 おぉ。コピー出来た。

 

 だけど、問題もある。以下のC++ファイルをコンパイルしてみる。

#include <iostream>
int main(int argc, char *argv[]){
  std::cout << "これは日本語です。" << std::endl;
}
g++ main.cpp

 で、a.exeを実行すると………
縺薙l縺ッ譌・譛ャ隱槭〒縺吶▒

 ………Shift_JIS滅びねぇかな。

 

 一応、以下のオプションを付加することで、この問題は回避出来る。

g++ main.cpp -fexec-charset=CP932

 んー。

 

結論 iconvを通す

 結局私はあれこれ考えた結果、MSYSのデフォルトに戻しました。だってSJIS嫌いだし。文字コード変更してどうなるか分からないからね。

 文字化けせずにクリップボードに転送するには、iconvを通してUTF-8をCP932に変換すれば良い。

echo "あいうえお" | iconv -f UTF-8 -t CP932 | pbcopy

 ペースト↓
あいうえお

 また、実は今回はechoではなく、catで読み出した内容をクリップボードに貼り付けたいという要求から調べだしたこと。私は基本的にテキストファイルではUTF-8しか使わないので、catの出力もUTF-8になる。(catの文字コードはファイル依存)
 なので、結局aliasを次の様にしました。

alias clip='/c/Windows/System32/clip.exe'
alias pbcopy='iconv -f UTF-8 -t CP932 | /c/Windows/System32/clip.exe'

 Windows標準のclipはCP932を受け取り、pbcopyはUTF-8を受け取るという感じにしておけば、だいたいの状況は使い分けられるんじゃないかな。うん。

 とりあえず、これでいいかなぁ。

Windowsでフォルダ内のファイルの改行コードを一括変換

 ディレクトリ内のファイルがメモ帳で改行が表示されない形式(LFかCR)だからCR+LFのWindows形式に一括変換したい。しかし、sedはおろか、shell環境も、エディタもない。メモ帳程度。ハゲそう。
 そんなときでもPowerShellさえあれば、一括変換出来るので、メモメモ。

Windows形式(CR+LF)に変換

 以下を実行すると、全てのファイルやディレクトリに対して処理するので注意。特定のファイルだけにしたい場合は後述のfilterを使う。

ls | foreach{ (cat $_) -join "`r`n" | set-content $_ }

 なお、文字コードは元のファイルが何であれ、Shift-JIS(正確にはパソコン環境依存)になる模様。UTF8で保存したい場合は、Set-Contentのオプション「-encoding UTF8」を利用する。

(ただし、PowerShell6からはBOMが付かなくなったので、6以上を使うことをお勧めします。ない場合は諦めてBOM食ってろ)

ls | foreach{ (cat -encoding UTF8  $_ ) -join "`r`n" | set-content -encoding UTF8 $_ }

 PowerShell2で文字化けしたことがあるので、catの方も付けといた方が無難かも知れない。5でも元と異なるフォーマットで保存しようとすると文字化けする模様。
 今のところ、PowerShell6では文字化けしたことはない。6いいね。

 (PowerShellはエセShellで、 |は並列起動しないのでこんな書き方が可能らしい)

Unix形式(LF)に変換

ls | foreach{ (cat $_) -join "`n" | set-content $_ }

Mac形式(CR)に変換

ls | foreach{ (cat $_) -join "`r" | set-content $_ }

特定の拡張子のファイルのみ変換したい場合

 grepとかなくても、ls(Get-ChildItem)のオプションにfilterがあるので、それで簡単なgrepできる。

例:拡張子が.txtだけを変換したい場合

ls -filter *.txt | foreach{ (cat $_ ) -join "`r`n" | set-content $_ }

サブディレクトリも再帰的に変換する場合

 lsコマンドがちょっと長くなる。

ls -r -n -file | foreach{ (cat $_ ) -join "`r`n" | set-content $_ }

 -r(-Recurse)で再帰化して、-n(-Name)で名前だけ出力させて、-fileでファイルだけをforeachに渡す。-filterの併用も可。

 cat(Get-Content)ってSystem.IO.FileSystemInfoをそのまま喰えないんだねぇ。

参考URL

www.atmarkit.co.jp
docs.microsoft.com

PowerShell実践ガイドブック ~クロスプラットフォーム対応の次世代シェルを徹底解説~

PowerShell実践ガイドブック ~クロスプラットフォーム対応の次世代シェルを徹底解説~