Haskell で Verilog を書く話 (2)
ここのところ毎週そうしているように、この土曜日も、昼からビールを飲んでダラダラすごす予定だったんですが、ちょっと気が変わって、(500ml 缶 2本ほど飲み終わってから、) ゆるふわHaskell入門会に行ってきました。そこで pheaver さんの Verilog パーサをいじったり、懇親会で @wakaba_faure さんとお話ししたり、うっかりガチ勢の2次会に紛れ込んでしまい、なに話してんだかさっぱり分からないなりに刺激を受けたりと、それなりに有意義な一日でした。
で、Verilog パーサをいじってた内容がちょうど、チュートリアルの内容について @kazu_yamamoto さんがツイッターで触れていたことにちょっと関わるので、ついでにここでご紹介しようかな、と。
間違いた。
「データがそのまま入力できる」、「リテラルが自由自在」とはどういうことでしょうか。
別段、難しい話ではありません。例えば、以下のようにデータ型を定義すると、その型の値を表すリテラルが自動的に、一緒に定義されるということです。*1
data Puella = Mado | Homu | Saya | Anko | Mami
これで、"Mado", "Homu", "Saya", "Anko", "Mami" というリテラルが、Puella 型の値のリテラルとして、Haskell コード中で扱えるようになります。試しに、このコードをファイルに保存して、ghci に読ませてみましょう。
*Main> let magi = Homu *Main> :t magi magi :: Puella
このように、"Homu" という文字列が、ソースコード中で有効な値リテラルとして扱われ、そのまま入力できることが分かります。
ところでここで、Puella 型の値、Homu に束縛された変数 magi を評価してみるとエラーになります。
*Main> magi <interactive>:9:1: No instance for (Show Puella) arising from a use of `print' Possible fix: add an instance declaration for (Show Puella) In a stmt of an interactive GHCi command: print it
これは、Puella 型の値を、印字用の文字列に変換する方法が定義されていないため、ghci が print 関数を使ってその値を表示することができない、というエラーです。*2
これは、そのまま "Homu" を評価してみても同じです。
*Main> Homu <interactive>:16:1: No instance for (Show Puella) arising from a use of `print' Possible fix: add an instance declaration for (Show Puella) In a stmt of an interactive GHCi command: print it
エラーメッセージにある通り、Puella 型を、Show 型クラスのインスタンスにすることで、この問題を解決することが出来ます。あるデータ型を Show クラスのインスタンスにするのは、自動導出という仕組みが使えるので簡単です。以下のようにコードを書き換えて、ghci に読み込み直してみましょう。
data Puella = Mado | Homu | Saya | Anko | Mami deriving Show
*Main> let magi = Homu *Main> magi Homu *Main> (Mado, Homu) (Mado,Homu)
後者の例は、まどほむにしてみたかっただけで、タプルにした意味は特にありません。要素が Show のインスタンスなら、そのタプルも Show のインスタンスになるというお話は、別にするつもりないです。とにかく、このように、あるデータ型の Show クラスのインスタンスを自動導出すると、そのデータ型の値を評価した結果、そのリテラル表現と同じ文字列が出力されるようになります。
このような、言語処理系に入力する文字列表現と、言語処理系が出力する文字列表現が、同じになる性質を同図像性 (homoiconicity) と言います。
括弧の世界の人たちが重要視する性質です。*3 Haskell も、このような homoiconic な言語です。
(追記: あとになって、homoiconicity の説明としてはなにか間違ってるような気がして仕方がないのですが、どなたかつっこんでくださいませんでしょーか……。)
で、Verilog パーサの話
前回、「38行の AST 表現を Pretty Print して 11行の RTL 出力が得られましたやったー」という (間抜けな) ことを書いたわけですが、そもそも、Pretty Printer に食わせて所望の出力を得るための AST をどうやって得るのか、という話です。*4 手っ取り早い方法があります。所望の RTL をパーサに食わせてみればいいわけです。同図像性から、その出力はそのまま Haskell ソースコードとして有効な表現のはずです。
*Language.Verilog> Text.Parsec.parseTest module_item "always @(posedge clk) q <= d;" AlwaysItem (EventControlStmt (EventControlExpr (EventPosedge (ExprVar (Ident "clk")))) (Just (NonBlockingAssignment (ExprVar (Ident "q")) Nothing (ExprVar (Ident "d")))))
印字された表現を、そのままコピペして Pretty Printer に食わしてみます。
*Language.Verilog> ppItem $ AlwaysItem (EventControlStmt (EventControlExpr (EventPosedge (ExprVar (Ident "clk")))) (Just (NonBlockingAssignment (ExprVar (Ident "q")) Nothing (ExprVar (Ident "d"))))) always @(posedge clk) q <= d;
バッチリですね。*5
ほかの例も試してみましょう。
*Language.Verilog> Text.Parsec.parseTest module_item "reg q;" RegDeclItem (RegDecl reg Nothing [RegVar (Ident "q") Nothing])
*Language.Verilog> ppItem $ RegDeclItem (RegDecl reg Nothing [RegVar (Ident "q") Nothing]) <interactive>:3:31: Not in scope: `reg' Perhaps you meant `rem' (imported from Prelude)
ダメだ!! なんだこりゃ!?
問題は、module_item パーサが返した値のうち、RegDecl 値コンストラクタの第1引数です。"reg" なんて変数はたしかにありません。
RegDecl reg Nothing [RegVar (Ident "q") Nothing]
RegDecl 値コンストラクタの型は
RegDecl :: RegType -> Maybe Range -> [RegVar] -> RegDecl
なので、"reg" にあたる部分は RegType 型の値がくるべきだと分かります。ここで、AST を定義しているソースコード、Language/Verilog/Syntax/AST.hs を見てみましょう。*6 RegType 型の定義とともに、RegType 型の Show 型クラスインスタンス宣言があります。
data RegType = Reg_reg -- ^ unsigned variable of any size = " | Reg_integer -- ^ signed 32-bit variable | Reg_time -- ^ unsigned 64-bit variable | Reg_real -- ^ double-precision floating point variable | Reg_realtime -- ^ (same as above) deriving (Eq, Ord, Bounded, Enum, Data, Typeable) instance Show RegType where show Reg_reg = "reg" show Reg_integer = "integer" show Reg_time = "time" show Reg_real = "real" show Reg_realtime = "real"
見つけました! おまわりさんこいつです! こいつが犯人です!! RegType 型の値を評価するとどうなるか、ちょっと見てみましょう。
*Language.Verilog> Reg_reg reg *Language.Verilog> Reg_integer integer
当然ですが、上記の show で定義されてるように文字列が印字されます。やはり当然ですが、これらの印字結果は、定義されてる値コンストラクタの表現とは異なりますので、これをこのままソースコード中に書いてもエラーになります。逆に、先に Pretty Printer に渡そうとしてエラーになった "reg" の部分を正しい値コンストラクタに直してやると、正しく Haskell プログラムとして読み込まれるようになります。
*Language.Verilog> ppItem $ RegDeclItem (RegDecl Reg_reg Nothing [RegVar (Ident "q") Nothing]) reg q;
このように、自動導出まかせにせずに独自の show を定義したりすると、同図像性がくずれてしまい、利用者が「ちょっと式を評価してみてどんな値になるか試してみよう」と思っても、よく分からない表現が印字されて困ったりすることがあります。よっぽどのことがないかぎり、自前の show を定義しようなどと思わない方がよいでしょう。値を、その値コンストラクタとは異なる表現の文字列へ変換する関数が欲しい場合も、当然ありますがその場合でも、たとえば toString などといった Show 型クラスとは関係ない関数を定義するべきです。
というわけで、ゆるふわ Haskell 入門会のハッカソンタイムでは、このような同図像性が壊れてる部分をちまちまなおしたりしていました。修正したコードを、Fork して github に上げておきましたので、よろしければご覧になってみてください。
*1:正確にはこれはリテラルではないと思うけど、リテラルと見なしても実用上問題なさそうに思う。
*2:ghci を起動していたら、:t print してみましょう。
*3:こわい山本さんももともとそちらの出自ですね。
*4:いや、まぁ、実際は、地道にソースコードを追っかけてるわけなんですけど……。
*5:ちょっと改行をいじりました。
*6:https://github.com/pheaver/netlist-verilog/blob/f3a0c46c3d0d8316c9471ea65cdc9dc6dba744e4/verilog/Language/Verilog/Syntax/AST.hs#L565