読者です 読者をやめる 読者になる 読者になる

MyHDL の紹介

MyHDL は Python の内部 DSL として実装された HDL( http://www.myhdl.org/doku.php/start によれば HDL and HVL (Hardware Verification Language) としている)です。

ダウンロード

http://sourceforge.net/projects/myhdl/files/ から myhdl-0.7.tar.gz をダウンロードします。

インストール

tarball を展開し、その中にある setup.py を管理者権限で実行します。私の環境では次のようなコマンドになりました。

$ gzcat myhdl-0.7.tar.gz | tar xf -
$ cd myhdl-0.7
$ sudo python2.7 setup.py install

高階関数とデコレータ

MyHDL が多用している Python の機能で、他の言語の利用者にはあまりなじみがないと思われる、高階関数とデコレータについて説明します。
Python では、例えば次のように、「関数を返す関数」といったものが簡単に扱えます。

$ python2.7
Python 2.7.3 (default, Dec 26 2012, 18:28:40)
[GCC 4.2.1 20070831 patched [FreeBSD]] on freebsd8
Type "help", "copyright", "credits" or "license" for more information.
>>> def gen_print_n(n):
...   def pr_n():
...     print n
...   return pr_n
...
>>> pr42 = gen_print_n(42)
>>> pr42
<function pr_n at 0x80103c500>
>>> pr42()
42

この例では、「「n を表示する関数」を返す関数 gen_print_n」を定義して呼び出し、その結果を変数 pr42 に入れて、さらに呼び出しています。このような、返す値や引数が関数である関数を、高階関数といいます。
続いてデコレータについて説明します。次のような、「関数を引数に取って、モディファイした関数を返す関数」を考えます。

>>> def before_after(f):
...   def b_a():
...     print "before"
...     f()
...     print "after"
...   return b_a
...
>>> def hello():
...   print "hello"
...
>>> hello()
hello
>>> foo = before_after(hello)
>>> foo()
before
hello
after

ここで、関数定義の前に @before_after のように、先頭に @ を付けてこのような高階関数の名前を書くと、モディファイされた関数が定義されます。この @before_after のような記述を「デコレータ」といいます。

>>> @before_after
... def hello2():
...   print "hello"
...
>>> hello2()
before
hello
after

以上の機能を、MyHDL では多用しますので、Python に馴染みのない方は覚えておいてください。

記述例

例として 4 ビット加算器を示します。

上から解説します。

fa0_s = Signal(bool())
fa0_co = Signal(bool())
fa1_s = Signal(bool())
fa1_co = Signal(bool())
fa2_s = Signal(bool())
fa2_co = Signal(bool())
fa3_s = Signal(bool())

下位の要素を作る前に、それに繋げる信号を作っておきます。Signal(bool()) で、1ビットの信号を定義します(多ビットの信号はあとで出てきた時に説明します)。

fa0 = FullAdder(a(0), b(0), ci, fa0_s, fa0_co)
fa1 = FullAdder(a(1), b(1), fa0_co, fa1_s, fa1_co)
fa2 = FullAdder(a(2), b(2), fa1_co, fa2_s, fa2_co)
fa3 = FullAdder(a(3), b(3), fa2_co, fa3_s, co)

子要素の定義です。ここで a(0) などは、多ビットの信号から 1 ビットを切り出しています。もしこれを a[0] のようにすると、単に値の参照になってしまって、信号線の接続であることが MyHDL に伝わりません。モジュールの入れ子構造の、外側から内側への向きでは、このように丸括弧を使って信号の一部を取り出してつなぐことができます( http://www.myhdl.org/doc/current/manual/modeling.html#structural-modeling の Converting between lists of signals and bit vectors というところを参照してください )。

@always_comb
def logic():
    s.next = concat(fa3_s, fa2_s, fa1_s, fa0_s)

@always_comb というデコレータで、組み合わせ論理の記述であることを示します(MyHDL により Verilog HDL を生成すると、assign 文か、センシティビティリストに入力が全て列挙された always 文が生成されます)。
next という属性への代入により、信号の値の更新を示します。concat は MyHDL のビット連結関数です。モジュールの入れ子構造の、内側から外側への向きでは、このようにして信号の合成を記述します。

return logic, fa0, fa1, fa2, fa3

子要素の定義を含め、定義したもの全てを返します。
FullAdder については省略します。

def HalfAdder(a, b, s, co):
    @always_comb
    def logic():
        tmp0 = a or b
        tmp1 = a and b
        s.next = tmp0 and not tmp1
        co.next = tmp1

    return logic

半加算器です。
ここで、演算子に & や | や ~ ではなく、and や or や not を使っていることに注意してください。1 ビットの信号の場合はこうします。ここで & や | を使うと、Verilog HDL で integer が生成されてしまいます。

def sim_driver(a, b, ci, s, co):
    @instance
    def drive():
        for i in range(16):
            for j in range(16):
                a.next = intbv(i)[4:]
                b.next = intbv(j)[4:]
                ci.next = False
                yield delay(1)
                print "%d + %d = %d, co = %s" % (a, b, s, co)
        (略)

    return drive

シミュレーションのためのデータを与える部分です。説明は省略します。

a = Signal(intbv()[4:])
b = Signal(intbv()[4:])
ci = Signal(bool())
s = Signal(intbv()[4:])
co = Signal(bool())

adder = Adder4(a, b, ci, s, co)

トップレベルのモジュールを定義します。Signal(intbv()[4:]) が 4 ビットの信号の定義です。

drv = sim_driver(a, b, ci, s, co)
sim = Simulation(adder, drv)
sim.run(1000)

シミュレーションの実行です。Verilog HDLを生成するだけなら必要ありませんが、MyHDL のスタンスとして、シミュレーションを走らせて異常が出ないことをチェックしてから変換しろ、ということのようですので( http://www.myhdl.org/doc/current/manual/conversion.html#methodology-notes を参照)、この例でもシミュレーションに必要なコードを付けました。実行するとシミュレーション結果がプリントされます。

toVerilog(Adder4, a, b, ci, s, co)

Verilog HDLの生成です。モジュール Adder4 が定義された Adder4.v というファイルと、テストベンチの雛形が書かれた tb_Adder4.v というファイルが出力されます。
以下に出力された Adder4.v の内容を示します。

見るとわかりますが、階層構造が展開された HDL 記述になっています。これは MyHDL の方針ということのようです( http://www.myhdl.org/doc/current/manual/conversion.html#methodology-notes を参照 )。
その他詳しくは、MyHDL のマニュアル( http://www.myhdl.org/doc/current/manual/index.html )に目を通してみると良いでしょう。
追記: 以下のブログエントリもよくまとまっています。