shtaxxx日記

コンピュータアーキテクチャについて研究している研究者の日記や技術紹介

PythonとVeriloggenで既存のVerilogモジュールを読み込んで改造する

前回に引き続きVeriloggenの話.今回は, read_verilog_module(), read_verilog_module_str() を使って,Verilog HDLで書かれた既存のハードウェア構成を取り込んで,更に改変する方法についてまとめます.

VeriloggenはPythonVerilog HDLのソースコードを組み立てることができるライブラリです. 今回の例はGitHubここにあります.

github.com

既存の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はPythonVerilog HDLのソースコードを組み立てるフレームワークです.

github.com

このソースコード一式は,ここにあります.

基本的な構成は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

動作波形はこんな感じ.

f:id:sxhxtxa:20150819010943p:plain

ちゃんとソートされている.

感想

Veriloggen,あんまり書きやすくないかも・・・.精進します. PythonなのにSynthesijer.Scalaよりもソースコードが長くなったのもイマイチ.

PyMTLを使ってLEDチカチカ回路を作ってみる

ACM/IEEE MICRO-48で発表されたPyMTLを試してみたので,その使い方をまとめておきます.

PyMTLはPython上でBehavior level, Cycle level, Register transfer levelの3種類の抽象度でハードウェアをモデリングすることができるフレームワークです.

github.com

ここでは,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両対応です.

github.com

pypi.python.org

PyCoRAMなどの高位合成(動作合成)系のようにPythonソースコードVerilog HDLに変換するのではなく,PythonVerilog 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

インデントが揃っていないので,emacsvimなどのテキストエディタで自動で修正してください.

シミュレーションをしてみる

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文字も書いていないのに,ハードウェアのシミュレーションができました!

f:id:sxhxtxa:20150831184600p:plain

まとめ

PythonVerilog HDLのASTを組み立てるためのライブラリVeriloggenを公開しました.Verilog HDLとしての文法チェック機能や,VPIや高位IP設計フレームワークのPyCoRAMとの連携,新たな抽象化レイヤーの追加など,課題は沢山ありますが,追々対応していきます.

追記

括弧が多くて対応関係が分かりづらいですので,括弧に色を付けるのがおすすめです. Emacsの人はこちらを参考に,rainbow-delimitersを導入すると良いと思います.

Zynq+PyCoRAM(+Debian)入門

Zynq + Vivado HLS入門Zynq + Synthesijer入門に引き続き,ARMを搭載するFPGAXilinx 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