Verilogパーサで抽象構文木を生成するようにした


Verilogファイルから、抽象構文木を生成できるようにしました。
処理できる構文はまだまだ限定的ですが。。


Haskell Parsecで直接扱うことのできない左再帰の構文を
再帰に変形している部分があるのですが (expression構文のところ)
ここをパースして右再帰に変形前の構造に戻して構文木を作る処理で
ちょっと頭がこんがらがりそうでしたが(こんがらがりました)。
(左再帰のところは、chainlとか chainl1をうまく使えばよいのかな??)



このブログを書いてる時点のソースコード
http://github.com/kei-os/vparsec/tree/ccd8e9ceea5794b7b64acc366725bd967f61aa08


データコンストラクタとかデータ型の名前に迷いがあったり
多相的な型を使えばパーサをもっと簡潔に書けるんじゃない?とか
いろいろ思うところはありますが、とにかく
Haskell慣れしながら改善していきたいと思います。


いまのところ、生成した抽象構文木を printで出力するしか
確認する方法を用意できていませんが
今後は、確認しやすい形式でダンプする仕組みを用意できればと思っています。
また、モジュールのインスタンス化など、モジュールの階層構造を
うまく表してみたいと思っています。
(あと、やっぱりこまめにブログに書いていこうかと思います。
 それとスピードが必要><)


とりあえず、実装したものをいくつかと、パース実行結果を記載しておきます。

  • 代数的データ型 (ソースから抜粋)
  • パース実行関数 (ソースから抜粋)
  • ghci実行例 (実行ログ)

代数的データ型


抽象構文木のノードに対応するデータ型を、次のように用意しました。
変更の可能性はありますが、いまのところ、ということで。


ふつけるでいうところの構造体スタイル、列挙型スタイル、共用体スタイルの
使い方になれてきました。
型クラスはまだうまく使えてない。
セレクタも使えてない。

data Module_ = MODULE
    { mName     :: String
    , mPorts    :: [String]
    , mItems    :: [ModuleItem_]
    } deriving (Eq, Show)

data ModuleItem_ = MI_PARAM_DECL    String              -- XXX TODO impl
                 | MI_CONT_ASSIGN   [NetAssign_]
                 | MI_PORT_DECL     Sig_
                 | MI_REG_DECL      Sig_
                 | MI_TIME_DECL     String              -- XXX TODO impl
                 | MI_INT_DECL      String              -- XXX TODO impl
                 | MI_NET_DECL      Sig_
                 | MI_INITIAL       Stmt_
                 | MI_ALWAYS        Stmt_
                   deriving (Eq, Show)

data Stmt_ = ST_BLOCKING_ASSIGN     BlockAssign_
           | ST_NON_BLOCKING_ASSIGN BlockAssign_
           | ST_PROCEDURAL_ASSIGN   RegAssign_
           | ST_TIMING_CONTROL_STMT TimingControl_
           | ST_CONDITIONAL_STMT    Condition_
--           | ST_CASE_STMT           String
--           | ST_LOOP_STMT           String
--           | ST_WAIT_STMT           String
--           | ST_DISABLE_STMT        String
--           | ST_EVENT_TRIGGER       String
             | ST_SEQ_BLOCK           Block_
--           | ST_PAR_BLOCK           String
--           | ST_TASK_ENABLE         String
--           | ST_SYSTEM_TASK_ENABLE  String
           | ST_NIL
             deriving (Eq, Show)

data RegAssign_ = REG_ASSIGN LValue_ Expr_ deriving (Eq, Show)        -- normal assignment (regAssignment)

data NBAssign_ = ASSIGN LValue_ DelayOrEvent_ Expr_ deriving (Eq, Show)     -- blockingAssignment
type BAssign_ = NBAssign_       -- nonBlockingAssignment

data NetAssign_ = NET_ASSIGN LValue_ Expr_ deriving (Eq, Show)

-- XXX first, support only "assign"
data ProcAssign_ = PROC_ASSIGN LValue_ Expr_ deriving (Eq, Show)

data TimingControl_ = TIMING_CONTROL DelayOrEvent_ Stmt_ deriving (Eq, Show)

data Condition_ = CONDITION CondExpr_ IfStmt_ ElseStmt_ deriving (Eq, Show)
type IfStmt_ = Stmt_
type ElseStmt_ = Stmt_

--data SeqBlock_ = SEQ_BLOCK Stmt_ NameOfBlock_ OutputDecl_ deriving (Eq, Show)   -- temp
--type NameOfBlock_ = String
--type OutputDecl_ = String       -- XXX temp

data DelayOrEvent_ = DE_DELAY_CONTROL DelayControl_
                   | DE_EVENT_CONTROL [Event_]
                   | DE_EXPR_EV Expr_ [Event_]
                   | DE_NIL
                     deriving (Eq, Show)

--data DelayControl_ = DL_NUM Integer | DL_IDENT String deriving (Eq, Show)
data DelayControl_ = DL_NUM Number_ | DL_IDENT String deriving (Eq, Show)

data Edge_ = POS | NEG | VALUE deriving (Eq, Show)
data Event_ = EVENT Edge_ Expr_ deriving (Eq, Show) -- new


data Primary_ = PR_NUMBER Number_       -- XXX TODO : test (Number_)
              | PR_IDENT String
              | PR_IDENT_EXPR String Expr_
              | PR_IDENT_RANGE String Range_
              | PR_CONCAT [Expr_]
              | PR_MINMAX_EXPR Expr_ Expr_ Expr_    -- min ( ,typ ,max)    -- XXX TODO : check order
                deriving (Eq, Show)

type UnaryOp_   = String
type BinaryOp_  = String
type CondExpr_  = Expr_
type IfExpr_    = Expr_
type ElseExpr_  = Expr_

data Expr_ = EX_PRIMARY Primary_
           | EX_U_PRIMARY UnaryOp_ Primary_
           | EX_NODE Expr_ BinaryOp_ Expr_             -- left op right
           | EX_COND CondExpr_ IfExpr_ ElseExpr_     -- cond ifexpr elseexpr
           | EX_STRING String
           | EX_NIL
             deriving (Eq, Show)

data LValue_ = LV_IDENT String
             | LV_IDENT_EXPR String Expr_
             | LV_IDENT_RANGE String Range_      -- identifier [ constant_expr : constant_expr ]
             | LV_CONCAT [Expr_]
               deriving (Eq, Show)

data BlockAssign_ = BLOCK_ASSIGN LValue_ DelayOrEvent_ Expr_ deriving (Eq, Show)

data BlockItem_ = BI_PARAM          -- XXX TODO impl
                | BI_REG Sig_
                | BI_INT            -- XXX TODO impl
                | BI_REAL           -- XXX TODO impl
                | BI_TIME           -- XXX TODO impl
                | BI_REALTIME       -- XXX TODO impl
                | BI_EVENT          -- XXX TODO impl
                  deriving (Eq, Show)

data Block_ = BLOCK String [BlockItem_] [Stmt_] deriving (Eq, Show)

------------------------------------------------------------

-- should i use Integer (not Int)??
type Typ_ = Int
--type Max_ = Int
type Max_ = Number_     -- XXX TODO : test
--type Min_ = Int
type Min_ = Number_     -- XXX TODO : test
--type Width_ = Int
type Width_ = String    -- XXX FIXME : temp for Number_
type Range_ = (Max_, Min_, Width_)

-- XXX TODO : reg / memory
data Sig_ = PORT_SIG { direction_ :: Direction_ , name_ :: [String] , range_ :: Range_ }
          | NET_SIG { netType_ :: NetType_, name_ :: [String], range_ :: Range_ }
          | REG_SIG { regType_ :: RegType_, name_ :: [String], range_ :: Range_ }
            deriving (Eq, Show, Ord)

data Direction_ = IN | OUT | INOUT | NONE deriving (Eq, Show, Ord)
data NetType_ = NET_WIRE | NET_TRI | NET_TRI1 | NET_SUPPLY0 | NET_WAND
              | NET_TRIAND | NET_TRI0 | NET_SUPPLY1 | NET_WOR | NET_TRIOR
                deriving (Eq, Show, Ord)

data RegType_ = REG | MEM deriving (Eq, Show, Ord)

data NumType_ = NUM_BIN | NUM_OCT | NUM_DEC | NUM_HEX deriving (Eq, Show, Ord)
data Number_ = NUMBER NumType_ String String deriving (Eq, Show, Ord)   -- type size value

パース実行関数

parseVerilogという関数にパースしたいファイル名を引数で渡して
パース処理を実行します。

parseVerilog :: FilePath -> IO ()
parseVerilog fname
    = do { input <- readFile fname
--         ; putStr input
         ; case parse verilog1995 fname input of
                Left err -> do { putStr "Error parsing at : " ; print err }
                Right x -> print x }

ghci実行例


たとえば次のような Verilogファイルを入力できます。

$ cat sample_dff.v

module sample_dff(
    clk,
    rst_n,
    en,
    d, q    
);

input clk, rst_n;
input en;
input [3:0] d;
output [3:0] q;

reg [3:0] q;

always@(posedge clk or negedge rst_n) begin
    if (!rst_n)     q <= 4'd0;
    else if (en)    q <= d;
    else            q <= q;
end

endmodule


GHCの対話型インタープリタ ghciを使った結果を貼っておきます。
MacOS X 10.5.6で GHC 6.10.1を使っています。

$ ghci
GHCi, version 6.10.1: http://www.haskell.org/ghc/  :? for help
Loading package ghc-prim ... linking ... done.
Loading package integer ... linking ... done.
Loading package base ... linking ... done.

Prelude> :l Vparsec
Ok, modules loaded: Vparsec.

Prelude Vparsec> parseVerilog "sample_dff.v"
Loading package parsec-2.1.0.1 ... linking ... done.
MODULE {mName = "sample_dff", mPorts = ["clk","rst_n","en","d","q"],
mItems = [MI_PORT_DECL (PORT_SIG {direction_ = IN, name_ = ["clk","rst_n"], 
range_ = (NUMBER NUM_DEC "1" "0",NUMBER NUM_DEC "1" "0","1")}),
MI_PORT_DECL (PORT_SIG {direction_ = IN, name_ = ["en"], 
range_ = (NUMBER NUM_DEC "1" "0",NUMBER NUM_DEC "1" "0","1")}),
MI_PORT_DECL (PORT_SIG {direction_ = IN, name_ = ["d"], 
range_ = (NUMBER NUM_DEC "" "3",NUMBER NUM_DEC "" "0","width on impl ")}),
MI_PORT_DECL (PORT_SIG {direction_ = OUT, name_ = ["q"], 
range_ = (NUMBER NUM_DEC "" "3",NUMBER NUM_DEC "" "0","width on impl ")}),
MI_REG_DECL (REG_SIG {regType_ = REG, name_ = ["q"], 
range_ = (NUMBER NUM_DEC "" "3",NUMBER NUM_DEC "" "0","width on impl ")}),
MI_ALWAYS (ST_TIMING_CONTROL_STMT 
(TIMING_CONTROL (DE_EVENT_CONTROL [EVENT POS (EX_PRIMARY (PR_IDENT "clk")),
EVENT NEG (EX_PRIMARY (PR_IDENT "rst_n"))]) (ST_SEQ_BLOCK (BLOCK "" [] [ST_CONDITIONAL_STMT (CONDITION (EX_U_PRIMARY "!" (PR_IDENT "rst_n"))
(ST_NON_BLOCKING_ASSIGN (BLOCK_ASSIGN 
(LV_IDENT "q") DE_NIL (EX_PRIMARY (PR_NUMBER (NUMBER NUM_DEC "4" "0"))))) 
(ST_CONDITIONAL_STMT (CONDITION (EX_PRIMARY (PR_IDENT "en")) (ST_NON_BLOCKING_ASSIGN (BLOCK_ASSIGN 
(LV_IDENT "q") DE_NIL (EX_PRIMARY (PR_IDENT "d")))) 
(ST_NON_BLOCKING_ASSIGN (BLOCK_ASSIGN 
(LV_IDENT "q") DE_NIL (EX_PRIMARY (PR_IDENT "q")))))))]))))]}


んん、確認しづらい...です。
やはりうまくダンプしたいところです。