pythonでナノ秒の世界へ!
ハードウェアロジックをpythonで記述する
tags: | python, hardware, hdl, module, tips |
---|---|
created: | 2006-12-31T17:19:52 |
MyHDLプロジェクトの紹介 ハードウェアロジックをpythonで記述する
概要
「 MyHDL 」は「 python 」によるハードウェアロジック記述に特化した 「 DSL 」の一種です。
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ギガバイトを軽く超えているのが泣き所ですが・・・)
以下の例はSpartan2「XS2S150」にインプリメントする例です。
プロジェクトの新規作成
HDLをトップレベルとするプロジェクトを作成してください。
ピン配置のマップ
ソースペインにて
「night_rider」を選択して
プロセスペインにて
「User Constrains」を展開して「Assign Package Pins」をダブルクリック
「Xilinx PACE」が起動しますので、以下のようにIO PinsのLOC部を編集します。 LOCにはピン番号を入力しますがボードの仕様にあわせてください。
この例におけるターゲットは「CQ出版 Xilinx SPARTAN-II評価ボード」です。 ターゲットによってはプルアップ、プルダウン、IO電圧などの指定が必要になる場合があります。
入力完了したら、保存したのちPACEを終了してください。
インプリメント
ソースペインにて
「night_rider」を選択して
プロセスペインにて
「Implement Design」をダブルクリック
ここでは実デバイスに対するフィッティングを行います。
Translate、Map、Place & Routeという手順を経由してImplementが完了します。
ビットストリーム作成とコンフィギュレーション
ターゲットデバイスとPCをJTAGケーブルで接続して、ターゲットの電源を入れておいてください。
ソースペインにて
「night_rider」を選択して
プロセスペインにて
「Generate Programinf File」を展開して「Configure Device」をダブルクリック
ビットストリームがジェネレートされた後、「iMPACT」というツールが起動します。
iMPACTのプロジェクトがない場合(初めてこのプロジェクトでiMPACT起動した時)は JTAGケーブルの特定ダイアログが出ます。 AutoのままFinishをクリックすればおおむね見つかるでしょう。 うまくいかない場合はターゲットのマニュアルを参照してください。
*.bitファイルの選択ダイアログにて、「night_rider.bit」ファイルを選択します。
するとワーニングがでますが、ここでは無視してください。
デバイスアイコンを右クリックして「Program」を選択。
ProgramingダイアログでOKを押すとターゲットのコンフィギュレーションが開始されます。
コンプリートするとターゲットの動作が開始します。
今回の例では8つのLEDのひとつだけ点灯していて、それが左右にスイングします。
まとめ
ナノ秒オーダーの動作を記述するのにPythonを拡張した MyHDLで記述できるということがわかりました。
そしてそれが、実機で動作するところまで確認しました。
記述をノーチェックでVerilogへの引渡しを行う部分あり Verilogとしての不適切な記述を検出できないところが一部ありますので、 幾らか慣れないとPythonと開発環境との行き来を必要としますが 慣れるとPythonだけで開発し最終段のみ開発環境を利用するという形で作業することができます。
こうなってしまえば、Pythonの柔軟性を活用してシミュレーションを構築できます。 グラフ表示、ビジュアライザ、作図等、Pythonが備える各種モジュールを活用することもできます。