Haskell で Verilog を書く話 (1)

えーまぁなんか、コードジェネレータ書いたよ! ってお話するとついったなんかでは、うんうん誰もが一度は通る道だよとかなんとか生暖かい目で見られたりなんかしたりするわけですが、はい。HaskellVerilog の RTL を生成するお話です。

ちなみに、RubyVHDL を生成するプログラムを書いたことなら、だいぶ前にあるんです。

HaskellVerilog ジェネレータを書くにしても同じ方針でいくのがいいよなー、と思ったところでまず思いつきました。対象言語の構文を表現する実装言語内 DSL ってつまり、構文解析器が生成する抽象構文木そのものじゃね? と。そして、HaskellVerilog パーサを書いたよ! とライブラリを公開してるひとはひとりやふたりではありません。*1 なにせパーサ記述 DSL と揶揄されるほど構文解析器が書きやすい Haskell です。

じゃぁ、その中からよさげなやつを探して、そいつで定義されてる抽象構文木を文字列に変換するプログラムを書けばいいわけです。こういう、なんらかのデータ構造を人間に読みやすい文字列に変換するプログラムを Pretty Printer と言いますね。*2 構文解析の逆変換を行うプログラムともいえます。Haskell構文解析器が書きやすいのは、Parser Combinator というライブラリが提供されているところに依りますが、Pretty Printer についても Pretty Printer Combinator なるものが提供されていたりします。

というわけでこの記事は、Haskell の Pretty Printer Combinator ライブラリの使い方を解説する記事、……ではないです。すみません。ここまで考えたところでもひとつ思いつきました。Verilog 用の Pretty Printer ももう誰かが書いて公開してたりするんじゃね? と。

……ありました。

https://github.com/pheaver/netlist-verilog

リポジトリ名から想像するにネットリストをあーだこうだするためのライブラリっぽいですが、どうやら Verilog 95 の文法をおよそ全部対応した構文解析器と Pretty Printer がついてるようです。さっそく試してみましょう。

$ git clone https://github.com/pheaver/netlist-verilog.git
$ cd netlist-verilog/verilog # 全部で4パッケージ含んでるみたいだけど他の3パッケージはここでは使わない
$ cabal install

……コンパイル出来ないじゃないですか。*3

そんなわけで、パッチです。*4

diff --git a/verilog/Language/Verilog/Syntax/Expression.hs b/verilog/Language/Verilog/Syntax/Expression.hs
index 164dde8..e5f66cc 100644
--- a/verilog/Language/Verilog/Syntax/Expression.hs
+++ b/verilog/Language/Verilog/Syntax/Expression.hs
@@ -86,7 +86,7 @@ instance Show Sign where
   show Pos = "+"
   show Neg = "-"
 
-intExpr :: Integral a => a -> Expression
+intExpr :: (Integral a, Show a) => a -> Expression
 intExpr x = ExprNum (IntNum Nothing Nothing Nothing (show x))
 
 data Number

このように修正すると、cabal install して使えるようになります。今度こそ試してみましょう。とりあえず簡単な非同期リセット、イネーブル付き FF の記述でも出力してみますか。

module Main where

import Language.Verilog
import Text.PrettyPrint (render)

main :: IO ()
main = putStr . render . ppVerilog $
  Verilog
  [ModuleDescription
   (Module (Ident "ff")
    [Ident "clk",Ident "rst_b",Ident "en",Ident "d",Ident "q"]
    [InputDeclItem (InputDecl Nothing [Ident "clk"])
    ,InputDeclItem (InputDecl Nothing [Ident "rst_b"])
    ,InputDeclItem (InputDecl Nothing [Ident "en"])
    ,InputDeclItem (InputDecl Nothing [Ident "d"])
    ,OutputDeclItem (OutputDecl Nothing [Ident "q"])
    ,RegDeclItem (RegDecl Reg_reg Nothing [RegVar (Ident "q") Nothing])
    ,AlwaysItem
     (EventControlStmt
      (EventControlExpr
       (EventOr
        (EventPosedge (ExprVar (Ident "clk")))
        (EventNegedge (ExprVar (Ident "rst_b")))))
      (Just
       (IfStmt (ExprUnary UTilde (ExprVar (Ident "rst_b")))
        (Just
         (NonBlockingAssignment
          (ExprVar (Ident "q"))
          Nothing
          (ExprNum (IntNum Nothing (Just "1") (Just BinBase) "0"))))
        (Just
         (IfStmt (ExprVar (Ident "en"))
          (Just
           (NonBlockingAssignment
            (ExprVar (Ident "q"))
            Nothing
            (ExprVar (Ident "d"))))
          Nothing)))))])]

このプログラムを実行すると、以下のような Verilog 記述が標準出力に出力されます。

module ff (clk, rst_b, en, d, q);
  input clk;
  input rst_b;
  input en;
  input d;
  output q;
  reg q;
  always @(posedge clk or negedge rst_b)
    if (~rst_b) q <= 1'b0;
    else if (en) q <= d;
endmodule

わずか、38行のプログラムで11行ものRTLが生成されました、やったぁ!!

嬉しくないよ!!??

……いや、まぁ、このテのプログラムは、なんらかの入力から記述を自動生成してこそです。もう少しいろいろ作ってみましょう。

……とりあえずここまでで記事にしてみるかな?

*1:例: id:kei-os2007 さん

*2:人間に読みやすいかどうかを無視すると Serializer になる、……のかな?

*3:2013-05-23 当時

*4:Pull Request 送るべきなのかもしれないけど、なんかもう3年も放置中みたいだし……。