FPGAの部屋

FPGAやCPLDの話題やFPGA用のツールの話題などです。 マニアックです。 日記も書きます。

FPGAの部屋の有用と思われるコンテンツのまとめサイトを作りました。ご利用ください。 http://marsee101.web.fc2.com/index.html

カテゴリ: Synthesijer

Synthesijer もひと通り、やってみたので、今度は”ZYBO用Ubuntu Linux のカメラで撮った画像にラプラシアンフィルタをかける”で使用した laplacian_filter.c のラプラシアンフィルタ部分のみのコードを Synthesijer でコンパイルすることにした。

ほとんど C のコードと同じだが、Java のコードに変更した laplacian_filter.java を下に示す。

// ラプラシアンフィルタ
// x0y0 x1y0 x2y0 -1 -1 -1
// x0y1 x1y1 x2y1 -1  8 -1
// x0y2 x1y2 x2y2 -1 -1 -1
public class laplacian_filter{
    public int lap_filter(int x0y0, int x1y0, int x2y0, int x0y1, int x1y1, int x2y1, int x0y2, int x1y2, int x2y2){
        int y;

        y = -x0y0 -x1y0 -x2y0 -x0y1 +8*x1y1 -x2y1 -x0y2 -x1y2 -x2y2;
        if (y<0)
            y = 0;
        else if (y>255)
            y = 255;
        return(y);
    }
}


これを Make ファイルを修正して make した。Makefile の内の関係ある部分を下に示す。

SYNTHESIJER ?= ../../bin

SOURCES = laplacian_filter.java
VHDL_SOURCES = $(SOURCES:.java=.vhd)
VERILOG_SOURCES = $(SOURCES:.java=.v)

all: $(SOURCES)
    java -cp $(SYNTHESIJER) synthesijer.Main --verilog --vhdl $(SOURCES)


実行後に、laplacian_filter.vhd と laplacian_filter.v ファイルができた。

VHDLのテストベンチ (sim.vhd) を作製して、laplacian_filter.vhd をテストした。
GHDL + GTKWave では面倒なので、ModelSim AE 10.1e を使用した。
図 1 にシミュレーション結果を示す。
f7fa60ea.png

図 1 laplacian_filter.vhd のシミュレーション結果1 - 1

プロトコルを説明する。
lap_filter_req を 1 クロック間 1 にアサートした次のクロックで、lap_filter_busy が 1 にアサートされて計算中を示す。
lap_filter_busy が 0 にディアサートされた時が計算が終了した時で、この時には、lap_filter_return にラプラシアンフィルタの値が確定されている。
lap_filter_busy が 1 にアサートされた時から 0 にディアサートされるまでの幅は 240 ns だった。クロックは100MHz, 10 ns なので、ラプラシアンフィルタの計算に 24 クロックかかっている。
図 2 を見ると、unary_expr_0010 ~ binary_expr_0018 まで 1 クロックごとに 演算しているのがわかる。
cfef6418.png

図 2 laplacian_filter.vhd のシミュレーション結果1 - 2

lap_filter_req の間隔を狭めると (for i in 0 to 10 loop)、正しい値が出力できなくなる。その様子を図 3 に示す。
85cbe734.png

図 3 laplacian_filter.vhd のシミュレーション結果2

図 1 と図 2 のシミュレーション結果の sim.vhd を下に示す。

-- sim.vhd
-- Testbench for laplacian_filter.vhd 
-- 2014/12/27 by marsee

library IEEE;
use IEEE.std_logic_1164.all;
use IEEE.numeric_std.all;

entity sim is
end sim;

architecture testbench of sim is
component laplacian_filter
  port (
    clk : in std_logic;
    reset : in std_logic;
    lap_filter_x0y0 : in signed(32-1 downto 0);
    lap_filter_x1y0 : in signed(32-1 downto 0);
    lap_filter_x2y0 : in signed(32-1 downto 0);
    lap_filter_x0y1 : in signed(32-1 downto 0);
    lap_filter_x1y1 : in signed(32-1 downto 0);
    lap_filter_x2y1 : in signed(32-1 downto 0);
    lap_filter_x0y2 : in signed(32-1 downto 0);
    lap_filter_x1y2 : in signed(32-1 downto 0);
    lap_filter_x2y2 : in signed(32-1 downto 0);
    lap_filter_req : in std_logic;
    lap_filter_busy : out std_logic;
    lap_filter_return : out signed(32-1 downto 0)
  );
end component;

constant clock_period : time := 10 ns;
constant delay : time := 1 ns;

signal clk : std_logic := '0';
signal reset : std_logic;
signal lap_filter_x0y0 : signed(32-1 downto 0);
signal lap_filter_x1y0 : signed(32-1 downto 0);
signal lap_filter_x2y0 : signed(32-1 downto 0);
signal lap_filter_x0y1 : signed(32-1 downto 0);
signal lap_filter_x1y1 : signed(32-1 downto 0);
signal lap_filter_x2y1 : signed(32-1 downto 0);
signal lap_filter_x0y2 : signed(32-1 downto 0);
signal lap_filter_x1y2 : signed(32-1 downto 0);
signal lap_filter_x2y2 : signed(32-1 downto 0);
signal lap_filter_req : std_logic;
signal lap_filter_busy : std_logic;
signal lap_filter_return : signed(32-1 downto 0);

begin
    clk <= not clk after clock_period/2; -- clk = 100MHz, 10 ns

    uut : laplacian_filter port map (
        clk                    => clk,
        reset                => reset,
        lap_filter_x0y0        => lap_filter_x0y0,
        lap_filter_x1y0        => lap_filter_x1y0,
        lap_filter_x2y0     => lap_filter_x2y0,
        lap_filter_x0y1     => lap_filter_x0y1,
        lap_filter_x1y1     => lap_filter_x1y1,
        lap_filter_x2y1     => lap_filter_x2y1,
        lap_filter_x0y2     => lap_filter_x0y2,
        lap_filter_x1y2     => lap_filter_x1y2,
        lap_filter_x2y2     => lap_filter_x2y2,
        lap_filter_req        => lap_filter_req,
        lap_filter_busy        => lap_filter_busy,
        lap_filter_return    => lap_filter_return
    );

    stimulus : process begin
        reset <= '1';
        lap_filter_x0y0 <= to_signed(0, 32);
        lap_filter_x1y0 <= to_signed(0, 32);
        lap_filter_x2y0 <= to_signed(0, 32);
        lap_filter_x0y1 <= to_signed(0, 32);
        lap_filter_x1y1 <= to_signed(0, 32);
        lap_filter_x2y1 <= to_signed(0, 32);
        lap_filter_x0y2 <= to_signed(0, 32);
        lap_filter_x1y2 <= to_signed(0, 32);
        lap_filter_x2y2 <= to_signed(0, 32);
        lap_filter_req <= '0';

        wait for 100 ns;
        reset <= '0';

        wait for 50 ns;

        lap_filter_x0y0 <= to_signed(127, 32);
        lap_filter_x1y0 <= to_signed(127, 32);
        lap_filter_x2y0 <= to_signed(127, 32);
        lap_filter_x0y1 <= to_signed(127, 32);
        lap_filter_x1y1 <= to_signed(127, 32);
        lap_filter_x2y1 <= to_signed(127, 32);
        lap_filter_x0y2 <= to_signed(0, 32);
        lap_filter_x1y2 <= to_signed(0, 32);
        lap_filter_x2y2 <= to_signed(0, 32);

        wait until clk'event and clk='1';
        wait for delay;
        lap_filter_req <= '1';
        wait until clk'event and clk='1';
        wait for delay;
        lap_filter_req <= '0';

        for i in 0 to 22 loop
            wait until clk'event and clk='1';
            wait for delay;
        end loop;

        lap_filter_x0y0 <= to_signed(127, 32);
        lap_filter_x1y0 <= to_signed(127, 32);
        lap_filter_x2y0 <= to_signed(127, 32);
        lap_filter_x0y1 <= to_signed(127, 32);
        lap_filter_x1y1 <= to_signed(81, 32);
        lap_filter_x2y1 <= to_signed(127, 32);
        lap_filter_x0y2 <= to_signed(0, 32);
        lap_filter_x1y2 <= to_signed(0, 32);
        lap_filter_x2y2 <= to_signed(0, 32);

        wait until clk'event and clk='1';
        wait for delay;
        lap_filter_req <= '1';
        wait until clk'event and clk='1';
        wait for delay;
        lap_filter_req <= '0';

        wait for 1 us;
        
        assert (false) report "Simulation End!" severity failure;
    end process;
end testbench;


最初にSynthesijer の関連資料まとめのページを紹介する。

Synthesijer で1つやってみたいことがある。

それは、いつものラプラシアンフィルタを実装してみたいということだ。Synthesijer にはAXI Master インターフェースを生成する機能は無いので、私の汎用AXI Master Module を使って制御してみたいという希望がある。

今までの結果からして、Sythesijer での実行はかなりクロックを消費してしまうので、ラプラシアンフィルタの演算を1クロックで行える形にHDLにしてくれるかどうかが最大の関心事だ。それがダメでも、消費クロック数分の演算器を並べてもよい。

Javeはあまり書いたことがないので、懸念材料とはなっている。Javaを見てはいるし、Androidのアプリも少しは書いたことがあるのだが、参考書のコールバック・ルーチンを少し変えたくらいなのだ。そこで、”やさしいJava”という参考書を買ったので、勉強しようと思っている。(やることは多すぎるのだが。。。やりたいことは多すぎて時間はとっても足りない。そう今もSynthesijer 、OpenCV のMatの勉強、Vivado HLS のAXI Steramの構築、etc.. を並列に考えている。そうPythonの勉強もあった)

さてその第1段階として、”WindowsでSynthesijerのsample/benchを試す”をやってみることにした。

Cygwin/X のインストール”で、Cygwin/X をインストールしたので、それ上で行う。

GHDL と GTKWaveのインストールは説明通りに行った。

cygwin上で環境変数SYNTHESIJERを設定するが、最初は絶対パスで設定していたが、やはりうまく行かなかった。やはり相対パスで指定する必要がある。現在は.bashrc に下のように記述してある。
export SYNTHESIJER="../../synthesijer-20141211.jar"
f094e193.png


ディレクトリ構造は、/home/Masaaki の下に、syntheijer ディレクトリを作製して、その下に、lib やsynthesijer-20141211.jar, synthesijer_samples_20141124 を置いている。lib も作った。
a82f5d0b.png


~/synthesijer/synthesijer_samples_20141124/bench で make した。
fc49b63e.png


make ghdl32 した。
7a971d06.png


ghdl -r primesim_top --wave=primesim_top.ghw を行った。時間が掛かった。
25473b2b.png


gtkwave primesim_top.ghw で、GTKWave を立ち上げる。これも時間がかかる。
c647a9c7.png

Test.java の上の Top.java があることがわかった。SoureForge の quickstart に一式があった。これを使用して、今回は実機で動作させてみようと思う。
使用するFPGAボードはZYBO だ。思えば、ZYBO はPLだけ、つまりFPGA部分だけで動作させたことがない。つまり、いつもハードウェアをエクスポートして、SDKでPL部をコンフィギュレーションしていた。今回はPSのクロックをあてに出来ないので、PL部へ外部から供給されたクロックを使用する。ZYBOには、イーサーネットのPHYから供給されている125MHzのクロックがあるので、これを使用する。
reset 入力はBTN0 を使用して、flag_return はLED1 を使用する。

java ファイルは、make.bat を作って、VHDL と Verilog HDL へ変換した。make.bat を示す。

java -cp C:\Users\Masaaki\Documents\Synthesijer\synthesijer-20141211.jar synthesijer.Main --vhdl --verilog^
                   Test.java ^
                   Top.java


ZYBO用のVivado プロジェクトを作製した。quickstart にあった sim.vhd もVivado プロジェクトに入れた。シミュレーションもしてみたが、やはり、5000001 カウントは遠い。なかなかflag_return が変化しないので、止めた。

論理合成、インプリメント、ビットストリームの生成を行った。
その際に、Test.xdc ファイルを作製した。Test.xdc ファイルを下に示す。

set_property PACKAGE_PIN L16 [get_ports clk]
set_property IOSTANDARD LVCMOS33 [get_ports clk]
set_property PACKAGE_PIN R18 [get_ports reset]
set_property IOSTANDARD LVCMOS33 [get_ports reset]
set_property PACKAGE_PIN M14 [get_ports flag_return]
set_property IOSTANDARD LVCMOS33 [get_ports flag_return]


ここからが初めて使用するVivado の機能(Vivado Logic Simulator では使用しているが、それを使用しないで、FPGAのコンフィギュレーションのみは、今回初めて)なのだが、Flow Navigator から Open Target をクリックして、Program Deviceをクリックしたら問題なくZynq のPL のみのコンフィギュレーションが成功した。
aa7d9805.png


LED0 も正常に点滅している。点滅周波数は、

125MHz / (5000001カウント * 7クロック * 2状態) ≒ 1.8 Hz

となるはずだ。

10秒間のLED0 の点滅回数を数えてみると、16回となった。少しズレが大きい気もするがだいたい合っている気がする。

Lチカする java ソフトウェア Test.java だが、私は違う書き方で書きたいので書いてみた。それをTest2.java とする。
Test2.java を下に示す。

public class Test2{
  public boolean flag;
  private int count;

  public void run(){
    while(true){
      if (count <= 10){
        count++;
      } else {
        count = 0;
        flag = !flag;
      }
    }
  }
}


これを、Synthesijer でコンパイルするために、Synthesijer の make.bat を参考にして私も make.bat を作製した。なお、Cygwinで Makefile を make してみたが、java コマンドを実行した所でエラーになってしまった。java のエラー・メッセージが日本語化けでわからないので、諦めて、Windows上でバッチファイルでコンパイルすることにした。make.bat を下に示す。

java -cp C:\Users\Masaaki\Documents\Synthesijer\synthesijer-20141211.jar synthesijer.Main --vhdl --verilog^
                   Test2.java 

make.bat を実行すると、Test2.vhd と Test2.v ができる。
テストベンチ Test2_tb.v は、前回の Test_tb.v と殆ど同じだ。
シミュレーションの結果を下の図に示す。
9fc59395.png


このソフトウェアだと、flag_out の周期は 1.92 usec となって、前回の 1.6 usec よりも長くなった。
前回は count を一つカウントアップするまでに7ステートだったが、今回は8ステートかかっているようだ。多分だが、こちらは、count++ するのに条件をつけているので、1クロック余計に必要としているのだろう?
この辺りの事情を汲んで java コードを書いたほうが良いかもしれない?

Test2.vhd を下に示す。

library IEEE;
use IEEE.std_logic_1164.all;
use IEEE.numeric_std.all;

entity Test2 is
  port (
    clk : in std_logic;
    reset : in std_logic;
    flag_in : in std_logic;
    flag_we : in std_logic;
    flag_out : out std_logic;
    run_req : in std_logic;
    run_busy : out std_logic
  );
end Test2;

architecture RTL of Test2 is


  signal clk_sig : std_logic;
  signal reset_sig : std_logic;
  signal flag_in_sig : std_logic;
  signal flag_we_sig : std_logic;
  signal flag_out_sig : std_logic;
  signal run_req_sig : std_logic;
  signal run_busy_sig : std_logic := '1';

  signal class_flag_0000 : std_logic := '0';
  signal class_flag_0000_mux : std_logic;
  signal tmp_0001 : std_logic;
  signal class_count_0001 : signed(32-1 downto 0) := (others => '0');
  signal binary_expr_00002 : std_logic := '0';
  signal unary_expr_00003 : signed(32-1 downto 0) := (others => '0');
  signal unary_expr_00004 : std_logic := '0';
  signal run_req_flag : std_logic;
  signal run_req_local : std_logic := '0';
  signal tmp_0002 : std_logic;
  type Type_run_method is (
    run_method_IDLE,
    run_method_S_0000,
    run_method_S_0001,
    run_method_S_0002,
    run_method_S_0003,
    run_method_S_0004,
    run_method_S_0005,
    run_method_S_0006,
    run_method_S_0007,
    run_method_S_0008,
    run_method_S_0009,
    run_method_S_0010,
    run_method_S_0012,
    run_method_S_0013,
    run_method_S_0014,
    run_method_S_0015  
  );
  signal run_method : Type_run_method := run_method_IDLE;
  signal run_method_delay : signed(32-1 downto 0) := (others => '0');
  signal tmp_0003 : std_logic;
  signal tmp_0004 : std_logic;
  signal tmp_0005 : std_logic;
  signal tmp_0006 : std_logic;
  signal tmp_0007 : std_logic;
  signal tmp_0008 : signed(32-1 downto 0);
  signal tmp_0009 : std_logic;

begin

  clk_sig <= clk;
  reset_sig <= reset;
  flag_in_sig <= flag_in;
  flag_we_sig <= flag_we;
  flag_out <= flag_out_sig;
  flag_out_sig <= class_flag_0000;

  run_req_sig <= run_req;
  run_busy <= run_busy_sig;
  process(clk)
  begin
    if clk'event and clk = '1' then
      if reset = '1' then
        run_busy_sig <= '1';
      else
        if run_method = run_method_S_0001 then
          run_busy_sig <= run_req_flag;
        end if;
      end if;
    end if;
  end process;


  -- expressions
  tmp_0001 <= flag_in_sig when flag_we_sig = '1' else class_flag_0000;
  tmp_0002 <= run_req_local or run_req_sig;
  tmp_0003 <= '1' and '1';
  tmp_0004 <= '1' and '0';
  tmp_0005 <= '1' when binary_expr_00002 = '1' else '0';
  tmp_0006 <= '1' when binary_expr_00002 = '0' else '0';
  tmp_0007 <= '1' when class_count_0001 <= X"0000000a" else '0';
  tmp_0008 <= class_count_0001 + X"00000001";
  tmp_0009 <= not class_flag_0000;

  -- sequencers
  process (clk)
  begin
    if clk'event and clk = '1' then
      if reset = '1' then
        run_method <= run_method_IDLE;
        run_method_delay <= (others => '0');
      else
        case (run_method) is
          when run_method_IDLE => 
            run_method <= run_method_S_0000;
          when run_method_S_0000 => 
            run_method <= run_method_S_0001;
            run_method <= run_method_S_0001;
          when run_method_S_0001 => 
            if run_req_flag = '1' then
              run_method <= run_method_S_0002;
            end if;
          when run_method_S_0002 => 
            if tmp_0003 = '1' then
              run_method <= run_method_S_0004;
            elsif tmp_0004 = '1' then
              run_method <= run_method_S_0003;
            end if;
          when run_method_S_0003 => 
            run_method <= run_method_S_0015;
          when run_method_S_0004 => 
            run_method <= run_method_S_0005;
          when run_method_S_0005 => 
            if tmp_0005 = '1' then
              run_method <= run_method_S_0007;
            elsif tmp_0006 = '1' then
              run_method <= run_method_S_0010;
            end if;
          when run_method_S_0006 => 
            run_method <= run_method_S_0014;
          when run_method_S_0007 => 
            run_method <= run_method_S_0008;
          when run_method_S_0008 => 
            run_method <= run_method_S_0009;
          when run_method_S_0009 => 
            run_method <= run_method_S_0006;
          when run_method_S_0010 => 
            run_method <= run_method_S_0012;
          when run_method_S_0012 => 
            run_method <= run_method_S_0013;
          when run_method_S_0013 => 
            run_method <= run_method_S_0006;
          when run_method_S_0014 => 
            run_method <= run_method_S_0002;
          when run_method_S_0015 => 
            run_method <= run_method_S_0000;
          when others => null;
        end case;
      end if;
    end if;
  end process;


  process(clk)
  begin
    if clk'event and clk = '1' then
      if reset = '1' then
        class_flag_0000 <= '0';
      else
        if run_method = run_method_S_0012 then
          class_flag_0000 <= unary_expr_00004;
        else
          class_flag_0000 <= class_flag_0000_mux;
        end if;
      end if;
    end if;
  end process;

  class_flag_0000_mux <= tmp_0001;

  process(clk)
  begin
    if clk'event and clk = '1' then
      if reset = '1' then
        class_count_0001 <= (others => '0');
      else
        if run_method = run_method_S_0008 then
          class_count_0001 <= unary_expr_00003;
        elsif run_method = run_method_S_0010 then
          class_count_0001 <= X"00000000";
        end if;
      end if;
    end if;
  end process;

  process(clk)
  begin
    if clk'event and clk = '1' then
      if reset = '1' then
        binary_expr_00002 <= '0';
      else
        if run_method = run_method_S_0004 then
          binary_expr_00002 <= tmp_0007;
        end if;
      end if;
    end if;
  end process;

  process(clk)
  begin
    if clk'event and clk = '1' then
      if reset = '1' then
        unary_expr_00003 <= (others => '0');
      else
        if run_method = run_method_S_0007 then
          unary_expr_00003 <= tmp_0008;
        end if;
      end if;
    end if;
  end process;

  process(clk)
  begin
    if clk'event and clk = '1' then
      if reset = '1' then
        unary_expr_00004 <= '0';
      else
        if run_method = run_method_S_0010 then
          unary_expr_00004 <= tmp_0009;
        end if;
      end if;
    end if;
  end process;

  run_req_flag <= tmp_0002;



end RTL;


Synthesijerを試してみる1(VHDLファイルを出力)”の続き。

今回はテストベンチ (Test_tb.v) を書いて、シミュレーションを行った。シミュレーション環境はVivado 2014.4のVivado Simulatorで行った。何故かと言うと、混在シミュレーションができるシミュレータが少ないからだ。ZYBOボードの xc7z010clg400-1 でプロジェクトを作製した。

シミュレータを行ったが、カウント数が多いので、シミュレーションが大変ということで、ループカウントを10 に変更した。変更したJava のコードを下に示す。

public class Test{
  public boolean flag;
  private int count;

  public void run(){
    while(true){
      count++;
      if(count > 10){
      //if(count > 5000000){
           count = 0;
           flag = !flag;
      }
    }
  }
}


これでシミュレーションを行った。Vivado Simulator の画面を下に示す。
6e8d3031.png


上の図から flag_out の周期は 1.6 usec となった。flag_out の値が反転するのは半分の 800 nsec である。それをカウント値の11カウントで割ると、

800 /11 ≒ 72.7 nsec

となる。現在の動作クロックは 100MHz で 10 nsec なので、1カウント分の平均的なクロック数は

72.7 / 10 = 7.27 クロック

となった。どうやら run_method_S_0011 の時に、flag_out が変化するようだ。ステートを遷移して行って判定を行いながら結果を出力しているのか?ユーザーズ・マニュアルが欲しいと思った。

下にテストベンチのTest_tb.v を示す。

`default_nettype none

`timescale 1ns / 100ps

// Test_tb.v
// 2014/12/17
//

module Test_tb;
    parameter DELAY = 1;

    wire    clk;
    wire    reset;
    reg        flag_in;
    reg        flag_we;
    wire    flag_out;
    reg        run_req;
    wire    run_busy;

    Test uut_Test (
        .clk(clk),
        .reset(reset),
        .flag_in(flag_in),
        .flag_we(flag_we),
        .flag_out(flag_out),
        .run_req(run_req),
        .run_busy(run_busy)
    );

    initial begin
        // Initialize Inputs
        flag_in <= 1'b0;
        flag_we <= 1'b0;
        run_req <= 1'b1;
    end

    // clk_gen のインスタンス(clk)
    clk_gen #(
        .CLK_PERIOD(10),    // 10nsec, 100MHz
        .CLK_DUTY_CYCLE(0.5),
        .CLK_OFFSET(0),
        .START_STATE(1'b0)
    ) ACLKi (
        .clk_out(clk)
    );

    // reset_gen のインスタンス
    reset_gen #(
        .RESET_STATE(1'b1),
        .RESET_TIME(100)    // 100nsec
    ) RESET_ARESETN (
        .reset_out(reset),
        .init_done()
    );
endmodule

module clk_gen #(
    parameter         CLK_PERIOD = 100,
    parameter real    CLK_DUTY_CYCLE = 0.5,
    parameter        CLK_OFFSET = 0,
    parameter        START_STATE    = 1'b0 )
(
    output    reg        clk_out
);
    begin
        initial begin
            #CLK_OFFSET;
            forever
            begin
                clk_out = START_STATE;
                #(CLK_PERIOD-(CLK_PERIOD*CLK_DUTY_CYCLE)) clk_out = ~START_STATE;
                #(CLK_PERIOD*CLK_DUTY_CYCLE);
            end
        end
    end
endmodule

module reset_gen #(
    parameter    RESET_STATE = 1'b1,
    parameter    RESET_TIME = 100 )
(
    output    reg        reset_out,
    output    reg        init_done
);
    begin
        initial begin
            reset_out = RESET_STATE;
            init_done = 1'b0;
            #RESET_TIME;
            reset_out = ~RESET_STATE;
            init_done = 1'b1;
        end
    end

endmodule

`default_nettype wire


↑このページのトップヘ