PythonとVeriloggenで既存のVerilogモジュールを読み込んで改造する
前回に引き続きVeriloggenの話.今回は, read_verilog_module(), read_verilog_module_str() を使って,Verilog HDLで書かれた既存のハードウェア構成を取り込んで,更に改変する方法についてまとめます.
VeriloggenはPythonでVerilog HDLのソースコードを組み立てることができるライブラリです. 今回の例はGitHubのここにあります.
既存のVerilog HDLのソースコードをインポートする
from veriloggen import * led_v = '''\ module blinkled # ( parameter WIDTH = 8 ) ( input CLK, input RST, output reg [WIDTH-1:0] LED ); reg [32-1:0] count; always @(posedge CLK) begin if(RST) begin count <= 0; end else begin if(count == 1023) begin count <= 0; end else begin count <= count + 1; end end end always @(posedge CLK) begin if(RST) begin LED <= 0; end else begin if(count == 1023) begin LED <= LED + 1; end end end endmodule ''' modules = read_verilog_module_str(led_v) m = modules['blinkled']
read_verilog_module_str(code)を使うと,テキスト形式のVerilog HDLのソースコードをVeriloggenの内部形式(Module)に変換して取り込むことができる. 辞書形式でモジュール一覧が返ってくるので,モジュール名をキーにしてモジュールに参照できる.
modules = read_verilog_module('led.v') m = modules['blinkled']
既存のVerilog HDLのファイル(.v)を取り込むにはread_verilog_module(*filename)を使う.返り値はテキストの場合と同様.
インポートしたVerilogモジュールを改造する
もちろんインポートしたモジュールをそのまま利用して,インスタンスを作成したりできる.Veriloggenではさらに,インポートしたモジュールを元に,拡張や変更がPythonのモジュールをいじくり回すだけでできる.
def mkLed(): modules = read_verilog_module_str(led_v) m = modules['blinkled'] # change the module name m.name = 'modified_led' # add new statements enable = m.Input('enable') busy = m.Output('busy') old_statement = m.always[0].statement[0].false_statement m.always[0].statement[0].false_statement = If(enable)(*old_statement) m.Assign( busy(m.variable['count'] < 1023) ) return m
この例ではまず,取り込んだblinkledモジュールの名前を'modified_led'に変更している.これはテキストの置換でできるくらいに簡単.
Veriloggenならではの機能としては,Verilog HDLのソースコードに後から,ポートを追加したり,回路を追加したりできる点がある.例では,入力ポートの'enable'や出力ポートの'busy'を追加している.そしてalways文の定義を変えたり,assign文を追加したりしている.具体的には,上で示したblinkledの1つ目のalways文でcountをインクリメントしているのだが,その条件にenableを追加している.加えて,countの値を使ってbusyを定義するassign文を追加している.
インポートした時点でModuleオブジェクトはユーザーがPythonで組み立てたModuleと全く同じなため,一から組み立てるのと同じように,m.Reg()やm.Always()などで信号や回路を後から自由に追加できる.簡単♪
Veriloggenのソースコード
import sys import os import collections from veriloggen import * led_v = '''\ module blinkled # ( parameter WIDTH = 8 ) ( input CLK, input RST, output reg [WIDTH-1:0] LED ); reg [32-1:0] count; always @(posedge CLK) begin if(RST) begin count <= 0; end else begin if(count == 1023) begin count <= 0; end else begin count <= count + 1; end end end always @(posedge CLK) begin if(RST) begin LED <= 0; end else begin if(count == 1023) begin LED <= LED + 1; end end end endmodule ''' def mkLed(): modules = read_verilog_module_str(led_v) m = modules['blinkled'] # change the module name m.name = 'modified_led' # add new statements enable = m.Input('enable') busy = m.Output('busy') old_statement = m.always[0].statement[0].false_statement m.always[0].statement[0].false_statement = If(enable)(*old_statement) m.Assign( busy(m.variable['count'] < 1023) ) return m if __name__ == '__main__': led_module = mkLed() led_code = led_module.to_verilog() print(led_code)
出力されるVerilog HDLのソースコード
module modified_led # ( parameter WIDTH = 8 ) ( input CLK, input RST, output reg [(((WIDTH - 1) + 1) - 1):0] LED, input enable, output busy ); reg [(((32 - 1) + 1) - 1):0] count; always @(posedge CLK) begin if(RST) begin count <= 0; end else if(enable) begin if((count == 1023)) begin count <= 0; end else begin count <= (count + 1); end end end always @(posedge CLK) begin if(RST) begin LED <= 0; end else begin if((count == 1023)) begin LED <= (LED + 1); end end end assign busy = (count < 1023); endmodule
入力Verilogコードと比べて,if(enable) という条件やassign busy = ... という定義が追加されているのがわかる.
何が嬉しいのか?
基本となる回路をVerilog HDLを書いておいて,それを特定のルールでチューニングしたりするのが自動できるようになります.あとはある回路をプロトタイピング用の小さなRTLに自動で変換したり,自作の高位合成処理系のコード生成器の一部として使ったりもできると思います.是非お試しください.
PythonとVeriloggenでソーティングネットワークを書いてみる
@miyox氏がSynthesijer.Scalaでソーティングネットワークを自動生成していたので,Veriloggenでも試してみた.
VeriloggenはPythonでVerilog HDLのソースコードを組み立てるフレームワークです.
基本的な構成はSynthesijer.Scala版とほぼ同じだけど,Veriloggenの方がVerilog HDLの文法に近く抽象化が少ない.
比較器
_i = [0] def mk_pair(): s = m.Wire('small_' + str(_i[0]), width) l = m.Wire('large_' + str(_i[0]), width) _i[0] += 1 return s, l def prim_net(a, b): s, l = mk_pair() m.Assign(s( Cond(a < b, a, b) )) # small m.Assign(l( Cond(a < b, b, a) )) # large return s, l
mk_pair()で比較用のwire変数を作成し,prim_net()でassignで代入する.比較器を組み合わせ回路として構成する.
ネットワーク1段分
def chain_net(regs, fsm, e): x = regs[0] for i in range(e): s, l = prim_net(x, regs[i+1]) fsm.add( regs[i](s) ) x = l fsm.add( regs[e](x) ) for i in range(e + 1, len(regs)): fsm.add( regs[i](regs[i]) ) fsm.goto_next()
chain_netでパイプラインステージ1段分の回路を組み立てる.Veriloggenはlib.fsmというステートマシンライブラリがあるのでそれを使う.
fsm自体は状態遷移機械を管理するオブジェクトである.そのadd()メソッドを呼び出すことで,各状態での変数の代入を行う.
fsm.goto_next()を呼び出すと,状態変数がひとつ増加し,その状態へ遷移するコードが追加される.fsm.goto_next()を使わずに次の状態に進めたい場合には,代入と同時にfsm.set_next()を呼び出すし,その後,fsm.inc()を呼び出せばよい.
組み立て
# build up fsm = lib.FSM(m, 'fsm') idle = fsm.current() # init state fsm.add(*[registers[i](inputs[i]) for i in range(numports)], cond=kick) fsm.add(busy(1), cond=kick) fsm.goto_next(cond=kick) # connect network for i in range(numports): chain_net(registers, fsm, numports-i-1) # finalize fsm.add(busy(0)) fsm.goto(idle)
fsmは状態遷移を管理するオブジェクト.fsm.current()で現在の状態遷移のラベルが取得できる.
まず,kickがアサートされたら,register*にinput*をコピーし,同時にbusyをアサートする.fsm.add()の名前付き引数でcond=を指定すると,代入する条件が指定出来る.If(cond)( hoge ) として条件を追加することのシンタックスシュガーである.
そして次の状態へ遷る.fsm.goto_next()の名前付き引数でcond=を指定すると,次の状態に遷移する条件を追加できる.指定しない場合やNoneを指定した場合には,無条件で遷移する.
あとは,chain_net()を規定回数呼び出して,ネットワーク全体を組み立てる.最後に,busyをデアサートして,idle状態に遷移して終了. fsm.goto_next()を呼び出すと,状態変数が自動的に1増えるが,fsm.goto()の場合には増えない.
ソーティングネットワークのソースコード全体
import sys import os from veriloggen import * nclk = lib.simulation.next_clock def mkSort(numports=4): m = Module('sort') width = m.Parameter('WIDTH', 32) clk = m.Input('CLK') rst = m.Input('RST') inputs = [ m.Input('input_' + str(i), width) for i in range(numports) ] outputs = [ m.Output('output_' + str(i), width) for i in range(numports) ] kick = m.Input('kick') busy = m.OutputReg('busy') registers = [ m.Reg('registers_' + str(i), width) for i in range(numports) ] for i in range(numports): m.Assign(outputs[i](registers[i])) _i = [0] def mk_pair(): s = m.Wire('small_' + str(_i[0]), width) l = m.Wire('large_' + str(_i[0]), width) _i[0] += 1 return s, l def prim_net(a, b): s, l = mk_pair() m.Assign(s( Cond(a < b, a, b) )) # small m.Assign(l( Cond(a < b, b, a) )) # large return s, l def chain_net(regs, fsm, e): x = regs[0] for i in range(e): s, l = prim_net(x, regs[i+1]) fsm.add( regs[i](s) ) x = l fsm.add( regs[e](x) ) for i in range(e + 1, len(regs)): fsm.add( regs[i](regs[i]) ) fsm.goto_next() # build up fsm = lib.FSM(m, 'fsm') idle = fsm.current() # init state fsm.add(*[registers[i](inputs[i]) for i in range(numports)], cond=kick) fsm.add(busy(1), cond=kick) fsm.goto_next(cond=kick) # connect network for i in range(numports): chain_net(registers, fsm, numports-i-1) # finalize fsm.add(busy(0)) fsm.goto(idle) init = [ busy(0) ] + [ r(0) for r in registers ] + [ fsm.set_init() ] # import assignment into always statement m.Always(Posedge(clk))( If(rst)( init ).Else( fsm.make_case() )) return m def mkSimSort(numports=4): m = Module('simsort') width = m.Parameter('WIDTH', 32) clk = m.Reg('CLK') rst = m.Reg('RST') inputs = [ m.Reg('input_' + str(i), width) for i in range(numports) ] outputs = [ m.Wire('output_' + str(i), width) for i in range(numports) ] kick = m.Reg('kick') busy = m.Wire('busy') uut = m.Instance(mkSort(numports), 'uut', (width,), [clk, rst] + inputs + outputs + [kick, busy]) lib.simulation.setup_waveform(m, uut) lib.simulation.setup_clock(m, clk) lib.simulation.setup_reset(m, rst) m.Initial( [ ip(100 - i) for i, ip in enumerate(inputs) ], kick(0), Wait(rst), nclk(clk), Wait(Not(rst)), nclk(clk), nclk(clk), nclk(clk), kick(1), nclk(clk), kick(0), ) m.Initial( Delay(100), Wait(kick), nclk(clk), Wait(busy), nclk(clk), Wait(Not(busy)), nclk(clk), Systask('finish'), ) return m if __name__ == '__main__': sort = mkSimSort() verilog = sort.to_verilog('tmp.v') print(verilog)
Veriloggenはまだテストベンチが生成できないので,テストベンチはVerilog HDLで書いた.面倒だった.
現在,テストベンチもVeriloggenで書けます.上記のmkSimSortを参照.
module test; initial begin $dumpfile("uut.vcd"); $dumpvars(0, uut); end parameter WIDTH = 32; reg CLK; reg RST; reg [(WIDTH - 1):0] input_0; reg [(WIDTH - 1):0] input_1; reg [(WIDTH - 1):0] input_2; reg [(WIDTH - 1):0] input_3; wire [(WIDTH - 1):0] output_0; wire [(WIDTH - 1):0] output_1; wire [(WIDTH - 1):0] output_2; wire [(WIDTH - 1):0] output_3; reg kick; wire busy; sort uut( CLK, RST, input_0, input_1, input_2, input_3, output_0, output_1, output_2, output_3, kick, busy ); initial begin CLK = 0; forever #5 CLK = ~CLK; end initial begin RST = 0; kick = 0; input_0 = 4; input_1 = 3; input_2 = 2; input_3 = 1; #100; RST = 1; #100; RST = 0; @(posedge CLK); #1; @(posedge CLK); #1; @(posedge CLK); #1; kick = 1; @(posedge CLK); #1; kick = 0; #500; $finish; end endmodule
実行結果とシミュレーション結果
生成されるVerilog HDLのコードはこんな感じ.なお現在のPyverilog.ast_code_generatorはちゃんと整形されたコードを生成します.
module simsort # ( parameter WIDTH = 32 ) ( ); reg CLK; reg RST; reg [(WIDTH - 1):0] input_0; reg [(WIDTH - 1):0] input_1; reg [(WIDTH - 1):0] input_2; reg [(WIDTH - 1):0] input_3; wire [(WIDTH - 1):0] output_0; wire [(WIDTH - 1):0] output_1; wire [(WIDTH - 1):0] output_2; wire [(WIDTH - 1):0] output_3; reg kick; wire busy; sort #( WIDTH ) uut ( CLK, RST, input_0, input_1, input_2, input_3, output_0, output_1, output_2, output_3, kick, busy ); initial begin $dumpfile("uut.vcd"); $dumpvars(0, uut); end initial begin CLK = 0; forever begin #5 CLK = (!CLK); end end initial begin RST = 0; #100; RST = 1; #100; RST = 0; end initial begin input_0 = 100; input_1 = 99; input_2 = 98; input_3 = 97; kick = 0; wait(RST); @(posedge CLK); #1; wait((!RST)); @(posedge CLK); #1; @(posedge CLK); #1; @(posedge CLK); #1; kick = 1; @(posedge CLK); #1; kick = 0; end initial begin #100; wait(kick); @(posedge CLK); #1; wait(busy); @(posedge CLK); #1; wait((!busy)); @(posedge CLK); #1; $finish; end endmodule module sort # ( parameter WIDTH = 32 ) ( input CLK, input RST, input [(WIDTH - 1):0] input_0, input [(WIDTH - 1):0] input_1, input [(WIDTH - 1):0] input_2, input [(WIDTH - 1):0] input_3, output [(WIDTH - 1):0] output_0, output [(WIDTH - 1):0] output_1, output [(WIDTH - 1):0] output_2, output [(WIDTH - 1):0] output_3, input kick, output reg busy ); reg [(WIDTH - 1):0] registers_0; reg [(WIDTH - 1):0] registers_1; reg [(WIDTH - 1):0] registers_2; reg [(WIDTH - 1):0] registers_3; assign output_0 = registers_0; assign output_1 = registers_1; assign output_2 = registers_2; assign output_3 = registers_3; reg [(32 - 1):0] fsm; localparam fsm_init = 0; localparam fsm_1 = 1; wire [(WIDTH - 1):0] small_0; wire [(WIDTH - 1):0] large_0; assign small_0 = (((registers_0 < registers_1))? registers_0 : registers_1); assign large_0 = (((registers_0 < registers_1))? registers_1 : registers_0); wire [(WIDTH - 1):0] small_1; wire [(WIDTH - 1):0] large_1; assign small_1 = (((large_0 < registers_2))? large_0 : registers_2); assign large_1 = (((large_0 < registers_2))? registers_2 : large_0); wire [(WIDTH - 1):0] small_2; wire [(WIDTH - 1):0] large_2; assign small_2 = (((large_1 < registers_3))? large_1 : registers_3); assign large_2 = (((large_1 < registers_3))? registers_3 : large_1); localparam fsm_2 = 2; wire [(WIDTH - 1):0] small_3; wire [(WIDTH - 1):0] large_3; assign small_3 = (((registers_0 < registers_1))? registers_0 : registers_1); assign large_3 = (((registers_0 < registers_1))? registers_1 : registers_0); wire [(WIDTH - 1):0] small_4; wire [(WIDTH - 1):0] large_4; assign small_4 = (((large_3 < registers_2))? large_3 : registers_2); assign large_4 = (((large_3 < registers_2))? registers_2 : large_3); localparam fsm_3 = 3; wire [(WIDTH - 1):0] small_5; wire [(WIDTH - 1):0] large_5; assign small_5 = (((registers_0 < registers_1))? registers_0 : registers_1); assign large_5 = (((registers_0 < registers_1))? registers_1 : registers_0); localparam fsm_4 = 4; localparam fsm_5 = 5; always @(posedge CLK) begin if(RST) begin busy <= 0; registers_0 <= 0; registers_1 <= 0; registers_2 <= 0; registers_3 <= 0; fsm <= fsm_init; end else begin case(fsm) fsm_init: begin if(kick) begin registers_0 <= input_0; registers_1 <= input_1; registers_2 <= input_2; registers_3 <= input_3; end if(kick) begin busy <= 1; end if(kick) begin fsm <= fsm_1; end end fsm_1: begin registers_0 <= small_0; registers_1 <= small_1; registers_2 <= small_2; registers_3 <= large_2; fsm <= fsm_2; end fsm_2: begin registers_0 <= small_3; registers_1 <= small_4; registers_2 <= large_4; registers_3 <= registers_3; fsm <= fsm_3; end fsm_3: begin registers_0 <= small_5; registers_1 <= large_5; registers_2 <= registers_2; registers_3 <= registers_3; fsm <= fsm_4; end fsm_4: begin registers_0 <= registers_0; registers_1 <= registers_1; registers_2 <= registers_2; registers_3 <= registers_3; fsm <= fsm_5; end fsm_5: begin busy <= 0; fsm <= fsm_init; end endcase end end endmodule
動作波形はこんな感じ.
ちゃんとソートされている.
感想
Veriloggen,あんまり書きやすくないかも・・・.精進します. PythonなのにSynthesijer.Scalaよりもソースコードが長くなったのもイマイチ.
PyMTLを使ってLEDチカチカ回路を作ってみる
ACM/IEEE MICRO-48で発表されたPyMTLを試してみたので,その使い方をまとめておきます.
PyMTLはPython上でBehavior level, Cycle level, Register transfer levelの3種類の抽象度でハードウェアをモデリングすることができるフレームワークです.
ここでは,Verilog HDLなどと同じRTLで回路を記述し,Verilog HDLのソースコードを生成してみます.
GitHubにあるPyMTLプロジェクトのREADMEとISCA2015でのチュートリアルのスライドを参考にして進めます.
PyMTLとVerilatorのインストール
あらかじめpython2.7とvirtualenvをインストールしておく.
pip install virtualenv
作業用ディレクトリを作成する.
mkdir pymtl
PATHの設定等を記述したスクリプトを用意する.setup.shという名前で作成する.
VERILATOR=~/pymtl/verilator/ source ./bin/activate export PATH=$VERILATOR/bin/:$PATH export PYMTL_VERILATOR_INCLUDE_DIR=$VERILATOR/include export VERILATOR_ROOT=$VERILATOR
反映させる.
source setup.sh
PyMTLとVerilatorをインストールする.
cd pymtl virtualenv --python=python2.7 . git clone https://github.com/cornell-brg/pymtl.git pip install --editable ./pymtl git clone http://git.veripool.org/git/verilator cd verilator/ autoconf export VERILATOR_ROOT=`pwd` ./configure make cd ..
PyMTLのテスト
cd pymtl mkdir build cd build py.test .. --verbose --tb=line py.test .. --verbose --test-verilog cd ../
LEDチカチカハードウェアをPyMTLで書いてみる
サンプルディレクトリを作成し,定義本体用ファイルとテスト用ファイルを用意する.
mkdir -p my_examples/led cd my_examples/led touch ledRTL.py touch ledRTL_test.py
定義本体のledRTL.pyはこんな感じ.
from pymtl import * class LedRTL( Model ): def __init__( s ): s.led = OutPort( 8 ) s.count = Wire( 32 ) s.led_count = Wire( 8 ) @s.tick_rtl def seq(): if s.count == 1023: s.count.next = 0 s.led_count.next = s.led_count + 1 else: s.count.next = s.count + 1 @s.combinational def comb(): s.led.value = s.led_count
テストのledRTL_test.pyはこんな感じ.
from pymtl import * from ledRTL import LedRTL def test_simple( test_verilog ): model = LedRTL() if test_verilog: model = TranslationTool( model ) model.elaborate()
実行してみる.
py.test ledRTL_test.py --test-verilog
そうすると,こんな感じのVerilog HDLのソースコードが同じディレクトリに生成されている.
//----------------------------------------------------------------------------- // LedRTL_0x791afe0d4d8c //----------------------------------------------------------------------------- // dump-vcd: False `default_nettype none module LedRTL_0x791afe0d4d8c ( input wire [ 0:0] clk, output reg [ 7:0] led, input wire [ 0:0] reset ); // register declarations reg [ 31:0] count; reg [ 7:0] led_count; // PYMTL SOURCE: // // @s.tick_rtl // def seq(): // if s.count == 1023: // s.count.next = 0 // s.led_count.next = s.led_count + 1 // else: // s.count.next = s.count + 1 // logic for seq() always @ (posedge clk) begin if ((count == 1023)) begin count <= 0; led_count <= (led_count+1); end else begin count <= (count+1); end end // PYMTL SOURCE: // // @s.combinational // def comb(): // s.led.value = s.led_count // logic for comb() always @ (*) begin led = led_count; end endmodule // LedRTL_0x791afe0d4d8c
想像した通りの回路がVerilog HDLで記述されている.
PyMTLの実装について
ハードウェアの各変数は,Modelクラスを継承したクラスのオブジェクトとして保持されています. Verilog HDLソースコードを生成する際には,Model継承クラスが持つ変数とその変数名の辞書をobj.__dict__を使って取得し,その中からハードウェア変数のPythonソースコード中の変数名を取得して,Verilog HDLでの変数名として利用しているようです.なるほど,かっこいい!
Veriloggen: PythonでVerilog HDLのソースコードを組み立てるためのライブラリ
海外出張の帰りの飛行機の中でちょっと暇だったので,Verilog HDLのソースコードをPythonで組み立てるためのライブラリを作りました.Python2.x, 3.x両対応です.
PyCoRAMなどの高位合成(動作合成)系のようにPythonのソースコードをVerilog HDLに変換するのではなく,PythonでVerilog HDLの抽象構文木(AST)を組み立てるためのライブラリです.
Veriloggenの名前は,PyCUDAなどで使われている,C/C++のソースコードをPythonで組み立てるライブラリのcgenに倣いました.
そのため,通常のRTL設計と同様に,プログラマが明示的にクロックサイクルレベルで回路の振る舞いを定義します. 同様のライブラリとしては,MyHDLがあります.MyHDLとの大きな違いは,Veriloggenは(現時点では)Pythonのastモジュールを使わずに,階層化されたオブジェクトとしてVerilog HDLのASTを直接組み立てる点です.
MyHDLでは,Pythonの文法のサブセットを用いてalways文風の定義を行うことができますが,その関数のソースコードはPythonのastモジュールを用いて解析されています.そのため,Pythonの処理系が本来持つすべての機能を利用することはできません.
一方で,Veriloggenは,Verilog HDLのASTの軽量な抽象化に留めることで,ASTの組み立てにPythonの処理系が本来持つすべての機能を利用することができます.もちろん,このままでは記述があまり楽ではないので,Veriloggenの上に更なる抽象化の階層を,別のライブラリとして実装するのが良いかもしれません.
VeriloggenとPyverilogのインストール
VeriloggenはPyverilogのASTのラッパーとして実装されています.そのためPyverilogとjinja2をインストールする必要があります.
Virtualenvの設定 (スキップ可能)
ここではせっかくなので,Pythonの仮想化環境であるvirtualenvを使って,新たにPyverilog, jinja2, Veriloggenをインストールしましょう.
virtualenvがシステムにインストールされていない場合には,pipコマンドを使ってインストールしましょう.
pip install virtualenv
仮想環境として新しいディレクトリを作り,virtualenvでPythonの環境を構築します.
mkdir veriloggen-test virtualenv veriloggen-test
まず,仮想環境に切り替えます.
cd veriloggen-test source bin/activate
Pyverilog, Jinja2, Veriloggenのインストール
ソースコード用ディレクトリを作成し,PyverilogとVeriloggenをcloneしましょう.
mkdir src cd src git clone https://github.com/shtaxxx/Pyverilog git clone https://github.com/shtaxxx/veriloggen
インストールします.
pip install jinja2 cd Pyverilog python setup.py install cd ../veriloggen python setup.py install cd ../
最新版はGitHub上にありますが,PyPIから安定版(?)をインストールすることもできます.
pip install pyverilog pip install veriloggen
LEDチカチカ回路を書いてみる
Veriloggenを使ってハードウェアを書いてみましょう.CLK, RST, LEDの3つのポートを持つモジュールを定義します. "led.py"という名前で以下のソースコードを書きましょう.
なんとなくVerilog HDLに似ている気がしませんか?
import sys import os from veriloggen import * def mkLed(): m = Module('blinkled') width = m.Parameter('WIDTH', 8) clk = m.Input('CLK') rst = m.Input('RST') led = m.OutputReg('LED', width) count = m.Reg('count', 32) m.Always(Posedge(clk))( If(rst)( count(0) ).Else( If(count == 1023)( count(0) ).Else( count(count + 1) ) )) m.Always(Posedge(clk))( If(rst)( led(0) ).Else( If(count == 1024 - 1)( led(led + 1) ) )) return m if __name__ == '__main__': led = mkLed() # led.to_verilog(filename='tmp.v') verilog = led.to_verilog() print(verilog)
実行してみましょう.MyHDLやPyCoRAMとは異なり,コードの実行によりはじめて,Verilog HDLのコードが生成されます.
python led.py
そうすると,以下の様なVerilog HDLのソースコードが出力されるはずです.
module blinkled # ( parameter WIDTH = 8 ) ( input CLK, input RST, output reg [(WIDTH - 1):0] LED ); reg [(32 - 1):0] count; always @(posedge CLK) begin if(RST) begin count <= 0; end else begin if((count == 1023)) begin count <= 0; end else begin count <= (count + 1); end end end always @(posedge CLK) begin if(RST) begin LED <= 0; end else begin if((count == 1023)) begin LED <= (LED + 1); end end end endmodule
インデントが揃っていないので,emacsやvimなどのテキストエディタで自動で修正してください.
シミュレーションをしてみる
Veriloggenは合成対象のVerilog HDLのソースコードだけではなく,シミュレーション用のソースコード(テストベンチ)の組み立てにも対応しています.
以下のソースコードはテストベントを含む場合のものです.
import sys import os from veriloggen import * def mkLed(): m = Module('blinkled') width = m.Parameter('WIDTH', 8) clk = m.Input('CLK') rst = m.Input('RST') led = m.OutputReg('LED', width) count = m.Reg('count', 32) m.Always(Posedge(clk))( If(rst)( count(0) ).Else( If(count == 1023)( count(0) ).Else( count(count + 1) ) )) m.Always(Posedge(clk))( If(rst)( led(0) ).Else( If(count == 1024 - 1)( led(led + 1) ) )) return m def mkTest(): m = Module('test') # target instance led = mkLed() # copy paras and ports params = m.copy_params(led) ports = m.copy_sim_ports(led) clk = ports['CLK'] rst = ports['RST'] uut = m.Instance(led, 'uut', params=m.connect_params(led), ports=m.connect_ports(led)) lib.simulation.setup_waveform(m, uut, m.get_vars()) lib.simulation.setup_clock(m, clk, hperiod=5) init = lib.simulation.setup_reset(m, rst, m.reset(), period=100) init.add( Delay(1000 * 100), Systask('finish'), ) return m if __name__ == '__main__': test = mkTest() verilog = test.to_verilog('tmp.v') print(verilog)
再度実行してみましょう.今度はテストベンチも一緒に生成されます.
python led.py
生成されたVerilog HDLのソースコードをシミュレーションしてみましょう.GTKwaveを使って波形を見てみましょう.
iverilog -Wall tmp.v ./a.out gtkwave uut.vcd &
こんな感じで回路の動作が波形で見えるはずです.Verilog HDLのソースコードを1文字も書いていないのに,ハードウェアのシミュレーションができました!
まとめ
PythonでVerilog HDLのASTを組み立てるためのライブラリVeriloggenを公開しました.Verilog HDLとしての文法チェック機能や,VPIや高位IP設計フレームワークのPyCoRAMとの連携,新たな抽象化レイヤーの追加など,課題は沢山ありますが,追々対応していきます.
追記
括弧が多くて対応関係が分かりづらいですので,括弧に色を付けるのがおすすめです. Emacsの人はこちらを参考に,rainbow-delimitersを導入すると良いと思います.
Zynq+PyCoRAM(+Debian)入門
Zynq + Vivado HLS入門とZynq + Synthesijer入門に引き続き,ARMを搭載するFPGAのXilinx Zynqの上でPyCoRAMを用いて生成したIPコアを利用するためのチュートリアルを作成しました.
Zynq上でLinuxを動作させることでおなじみのソフトウェアを利用できて便利なので,今回はLinuxとの連携方法についても合わせてまとめてみました.Pythonコントロールスレッド部のみを用いた設計も可能ですので,Python-Verilog HDLな高位合成としても使えます.
流れとしては,まずPyCoRAMのみでシミュレーションで動作検証し,その後VivadoでZynqに組み込みます.PyCoRAMは自動的にAXI4/AvalonのIPコアを生成するだけではなく,テストベンチなどの生成も行うため,機能検証がスムーズに行えます. ビットストリームができあがったら,U-Bootと呼ばれるブートローダを生成し,SDKでFSBLを作成します.ここで,前2つのチュートリアルと同様に,ベアメタルのアプリケーションを作成して動作させることもできます. あとは,Linuxカーネルのビルド・デバイスツリーの構築・ファイルシステムの構築を経て,最後にプログラムを書けば完成です.
なお,PyCoRAMは完全にGitHubベースのオープンソース開発に移行しました.何か提案・修正箇所等があれば,お知らせいただけますと助かります.
www.slideshare.net
ディスクイメージの大きさを変えるには
まずディスクイメージ一覧を確認する.
VBoxManage list hdds
サイズを変更する.MB単位で大きさを指定する.例の場合では80GB.
VBoxManage modifyhd "/Users/username/VM/Ubuntu/Ubuntu.vdi" --resize 81920