Haskell で Verilog を書く話 (1)
えーまぁなんか、コードジェネレータ書いたよ! ってお話するとついったなんかでは、うんうん誰もが一度は通る道だよとかなんとか生暖かい目で見られたりなんかしたりするわけですが、はい。Haskell で Verilog の RTL を生成するお話です。
ちなみに、Ruby で VHDL を生成するプログラムを書いたことなら、だいぶ前にあるんです。
Haskell で Verilog ジェネレータを書くにしても同じ方針でいくのがいいよなー、と思ったところでまず思いつきました。対象言語の構文を表現する実装言語内 DSL ってつまり、構文解析器が生成する抽象構文木そのものじゃね? と。そして、Haskell で Verilog パーサを書いたよ! とライブラリを公開してるひとはひとりやふたりではありません。*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
そんなわけで、パッチです。*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年も放置中みたいだし……。