pythonでナノ秒の世界へ!

ハードウェアロジックをpythonで記述する

tags:python, hardware, hdl, module, tips
created:2006-12-31T17:19:52

MyHDLプロジェクトの紹介 ハードウェアロジックをpythonで記述する

概要

「 MyHDL 」は「 python 」によるハードウェアロジック記述に特化した 「 DSL 」の一種です。

digraph myhdl {
subgraph cluster1 {
label="Pythonレベル"
"MyHDL記述"->"MyHDL記述"[label="テスト"]
}

subgraph cluster2 {
label="HDLレベル"
"Verilog記述"->"Verilog記述"[label="テスト"]
}

"MyHDL記述"->"Verilog記述"[label="toVerilog"]
}

MyHDLはPythonにいくつかの制約と拡張を行った記述になっています。 ほとんどの基本的なPythonの記述方法が使えます。

  • Verilogへのトランスレート
  • Pythonレベルのシミュレーションテスト

という2つの主要機能によって 「ハードウェアロジックの開発」を「Pythonベース」で行うことができます。

インストール

python2.4以降のインストールを済ませて置いてください。

「myhdl-0.5.1.tar.gz」を解凍し、解凍後のフォルダをカレントフォルダにして 以下のコマンドをコマンドプロンプトで実行してください。

> setup.py install

特長

最小の仕様定義

後述の定義された数種のクラスといくつかのユーティリティクラスのみで MyHDLが構成されています。

あとは、Pythonが備えているデコレータ、ジェネレータ機構をうまく利用して ハードウェアロジック開発を可能にしています。

いくつかのクラス定義

Signal
入出力ポートの宣言を行うためのラッパークラス
intbv
ビットベクター(配列)を整数として扱うラッパークラス
enum
列挙型宣言用のクラス、ステートマシンを作成する時に使います。

いくつかのデコレータ定義

@always

これを指定された関数宣言は瞬時処理を記述します。

ある基準をトリガにして時系列処理する内容を記述します。

@always_comb

これを指定された関数宣言内では継続処理を記述します。

時系列を無視して成り立つ式を記述します。

@instance

テスト用インスタンス宣言

Pythonジェネレータ記述をつかって シーケンシャルにテストコードを記述できます。

Verilogトランスレータ

開発ロジックは1関数単位でVerilogへ翻訳することができます。 ただし、その関数が他のロジックを記述した関数を参照していれば、 数珠繋ぎに翻訳されるようになっています。

注釈

開発中のバージョン0.6ではVHDLへのトランスレータも試験的に導入されています。 試したところ、VHDLのコンパイラでエラーが出るなどまだ致命的な問題が残っているようです。

シミュレーションサンプル

ものすごくシンプルなサンプルですが、 ナイトライダー風のLED点灯ロジックを作成してみました。

参考:

サンプルソース

from myhdl import *

ACTIVE_LOW = 0                     # for negative-logic
MAIN_CLOCK = 32E6                  # 32MHz
TIME_STEP = int(1/MAIN_CLOCK/1E-9) # nano-sec
DIV_TIMER = int(MAIN_CLOCK/32)

def night_rider(clock, reset, led_on, led_out, division):

  position = Signal(intbv(0)[3:])
  dir_inc = Signal(bool(0))
  counter = Signal(intbv(0, 0, division))

  @always(clock.posedge, reset.negedge)
  def main_proc():
    if reset==ACTIVE_LOW:
      position.next = 1
      dir_inc.next = 0
      counter.next = 0
    else:
      if counter==division-1:
        counter.next=0
      else:
        counter.next = counter + 1
      if counter==0:
        if position==1 and dir_inc==0:
          dir_inc.next = 1
        elif position==6 and dir_inc==1:
          dir_inc.next = 0
        if dir_inc:
          position.next = position + 1
        else:
          position.next = position - 1

  @always_comb
  def combination():
    led_out.next = 1<<position
    led_on.next = 0

  return main_proc, combination

clock = Signal(bool(1))
reset = Signal(bool(1))
led_on = Signal(bool(1))
led_out = Signal(intbv(0)[8:])

toVerilog(night_rider, clock, reset, led_on, led_out, DIV_TIMER)

def test_nr():
  inst = night_rider(clock, reset, led_on, led_out, 1)
  backup = Signal(intbv(0)[8:])

  @always(delay(TIME_STEP/2))
  def clkgen():
      clock.next = not clock

  @instance
  def stimulus():
    reset.next = 0
    yield clock.posedge
    reset.next = 1
    while 1:
      backup.next = led_out
      yield clock.posedge
      if backup!=led_out:
        print '%5dns:led=' % now(),
        s = ''
        for i in range(8):
          if (led_out<<i)&0x80:
            s += 'o'
          else:
            s += 'x'
        print s

  return inst, clkgen, stimulus

sim = Simulation(test_nr())
sim.run(16*TIME_STEP)

night_riderのdivisionという引数はインスタンス化オプションとしています。

divisionには実機用の分周比を指定しますが、 シミュレーション時に本気で実機の周波数動作をシミュレーションさせると、 とんでもない時間が掛かりますので、

シミュレーション用では「1」でインスタンス化しています。 (当然、以下のnow()値の表示はめちゃくちゃ短くなってしまいますが・・・)

シミュレーション結果

>python -u "nr.py"
   90ns:led= xxxxxxxo
  120ns:led= xxxxxxox
  150ns:led= xxxxxoxx
  180ns:led= xxxxoxxx
  210ns:led= xxxoxxxx
  240ns:led= xxoxxxxx
  270ns:led= xoxxxxxx
  300ns:led= oxxxxxxx
  330ns:led= xoxxxxxx
  360ns:led= xxoxxxxx
  390ns:led= xxxoxxxx
  420ns:led= xxxxoxxx
  450ns:led= xxxxxoxx
  480ns:led= xxxxxxox
_SuspendSimulation: Simulated 496 timesteps
>Exit code: 0

このように、LEDの点灯状況のシミュレーション結果をPythonだけで確認できます。

また、「toVerilog」の時点で「night_rider.v」というファイル名でVerilogコードが 出力されています。

アウトプット

module night_rider (
    clock,
    reset,
    led_on,
    led_out
);

input clock;
input reset;
output led_on;
wire led_on;
output [7:0] led_out;
wire [7:0] led_out;

reg [19:0] counter;
reg dir_inc;
reg [2:0] position;


always @(posedge clock or negedge reset) begin: _night_rider_main_proc
    if ((reset == 0)) begin
        position <= 1;
        dir_inc <= 0;
        counter <= 0;
    end
    else begin
        if ((counter == (1000000 - 1))) begin
            counter <= 0;
        end
        else begin
            counter <= (counter + 1);
        end
        if ((counter == 0)) begin
            if (((position == 1) && (dir_inc == 0))) begin
                dir_inc <= 1;
            end
            else if (((position == 6) && (dir_inc == 1))) begin
                dir_inc <= 0;
            end
            if (dir_inc) begin
                position <= (position + 1);
            end
            else begin
                position <= (position - 1);
            end
        end
    end
end


assign led_out = (1 << position);
assign led_on = 0;

endmodule

およびテストベンチコードが出力されています。

インプリメントサンプル

「Xilinx ISE Web-Pack ver 8.2.03i」を利用してインプリメントしてみました。 この開発環境はレジストすれば誰でも利用することができます。 (ダウンロードファイル一式が1ギガバイトを軽く超えているのが泣き所ですが・・・)

../../_images/xilinx_splash.png ../../_images/ise8203.png

以下の例はSpartan2「XS2S150」にインプリメントする例です。

プロジェクトの新規作成

HDLをトップレベルとするプロジェクトを作成してください。

ソースの追加

ソースペインにて

../../_images/xilinx_proj.png

右クリックメニューから「Add Source」

../../_images/xilinx_add.png

ファイルダイアログで、トランスレート出力された「night_rider.v」を指定します。

シンセサイズ

ソースペインにて

../../_images/xilinx_proj.png

「night_rider」を選択して

プロセスペインにて

../../_images/xilinx_proc.png

「Synthesize」をダブルクリック

ここでは高レベル記述をRTLレベルに落とし込みます。

ピン配置のマップ

ソースペインにて

../../_images/xilinx_proj.png

「night_rider」を選択して

プロセスペインにて

../../_images/xilinx_proc.png

「User Constrains」を展開して「Assign Package Pins」をダブルクリック

「Xilinx PACE」が起動しますので、以下のようにIO PinsのLOC部を編集します。 LOCにはピン番号を入力しますがボードの仕様にあわせてください。

../../_images/xilinx_assign.png

この例におけるターゲットは「CQ出版 Xilinx SPARTAN-II評価ボード」です。 ターゲットによってはプルアップ、プルダウン、IO電圧などの指定が必要になる場合があります。

入力完了したら、保存したのちPACEを終了してください。

インプリメント

ソースペインにて

../../_images/xilinx_proj.png

「night_rider」を選択して

プロセスペインにて

../../_images/xilinx_proc.png

「Implement Design」をダブルクリック

ここでは実デバイスに対するフィッティングを行います。

Translate、Map、Place & Routeという手順を経由してImplementが完了します。

ビットストリーム作成とコンフィギュレーション

ターゲットデバイスとPCをJTAGケーブルで接続して、ターゲットの電源を入れておいてください。

ソースペインにて

../../_images/xilinx_proj.png

「night_rider」を選択して

プロセスペインにて

../../_images/xilinx_proc.png

「Generate Programinf File」を展開して「Configure Device」をダブルクリック

ビットストリームがジェネレートされた後、「iMPACT」というツールが起動します。

iMPACTのプロジェクトがない場合(初めてこのプロジェクトでiMPACT起動した時)は JTAGケーブルの特定ダイアログが出ます。 AutoのままFinishをクリックすればおおむね見つかるでしょう。 うまくいかない場合はターゲットのマニュアルを参照してください。

../../_images/xilinx_bit.png

*.bitファイルの選択ダイアログにて、「night_rider.bit」ファイルを選択します。

するとワーニングがでますが、ここでは無視してください。

デバイスアイコンを右クリックして「Program」を選択。

ProgramingダイアログでOKを押すとターゲットのコンフィギュレーションが開始されます。

コンプリートするとターゲットの動作が開始します。

今回の例では8つのLEDのひとつだけ点灯していて、それが左右にスイングします。

../../_images/led_array.jpg

まとめ

ナノ秒オーダーの動作を記述するのにPythonを拡張した MyHDLで記述できるということがわかりました。

そしてそれが、実機で動作するところまで確認しました。

記述をノーチェックでVerilogへの引渡しを行う部分あり Verilogとしての不適切な記述を検出できないところが一部ありますので、 幾らか慣れないとPythonと開発環境との行き来を必要としますが 慣れるとPythonだけで開発し最終段のみ開発環境を利用するという形で作業することができます。

こうなってしまえば、Pythonの柔軟性を活用してシミュレーションを構築できます。 グラフ表示、ビジュアライザ、作図等、Pythonが備える各種モジュールを活用することもできます。