Parallella Fan!
Home記事一覧BBS

マンデルブロ集合の浮動小数点演算をFPGAで並列化する

【更新履歴】
2015/04/27 公式セットアップスクリプトに準拠する形に修正しました。以前にダウンロードされた方は再度ダウンロードしなおしてください。
2015/04/24 固定小数点版とのベンチマーク比較のために若干修正。
2015/03/29 プロジェクトのMakefileを修正しました。以前にダウンロードされた方は再度ダウンロードしなおしてください。
2015/03/11 新規公開

JavaのThreadを使って並列化したマンデルブロ集合のプログラムをSynthesijerでHDLに変換してFPGAに実装します。

ZYNQ-7020版のParallellaに実装して実行している様子です。
512x512画素の演算、描画を行っています。
並列化は、横1ライン分の演算を行うクラスを作り、そのインスタンスを8個作って同時に走らせる方法で行っています。(これはFPGA上で8個の演算コアとして実装されます。)1インスタンスの場合と比較して7.95倍スピードアップできています。(描画も含めた実測値)
描画と各演算コアの制御はシングルスレッドで行っています。

プロジェクトの準備

「SynthesijerでJavaプログラムからHDLコードを自動生成する」で作成したプロジェクトを使用し、「synthesijer_template」のIPを上書きして生成します。始めての方はまずこのチュートリアルを済ませてください。以下では今回のIPの相違点のみ解説します。

ソースコードのダウンロードとHDLコードの生成

端末で、

cd ~/parallella_hw_projects/user_ip/edk/pcores

wget -O synthesijer_template_mandelbrot.tar.gz https://cellspe.matrix.jp/files/synthesijer_template_mandelbrot.tar.gz

tar xzf synthesijer_template_mandelbrot.tar.gz

Synthesijerを実行してJavaプログラムからVerilog HDLコードを生成します。

※ZYNQ-7010版 Parallellaではロジック容量が少ないので8コアは実装できません。1コア版「Template.java.1core.bak」のソースコードを「Template.java」に入れ替えて試してください。

cd synthesijer_template_v1_00_a/devl/synthesijer

make

これで Template.java と Mandel.java から Template.v と Mandel.v (Verilog HDLファイル)が生成されて、synthesijer_template_v1_00_a/hdl/verilog ディレクトリにコピーされます。

前回のテンプレートIPからの変更点は、Template.java の改造、Mandel.java の追加、「devl/synthesijer/Makefile」の12行目に「cp Mandel.v ../../hdl/verilog/」を追加、「data/synthesijer_template_v2_1_0.pao」の3行目に「lib synthesijer_template_v1_00_a Mandel.v verilog」を追加、となっています。
独自プログラムを作成して .java ファイルを追加した場合にはこれと同様な修正を行ってください。

浮動小数点演算IPのチューニング

必須ではありませんが、浮動小数点演算IPのパラメータをチューニングすると、デフォルト設定よりも演算が速くなります。
PlanAhead の Project Manager → Sources ウィンドウで「fadd32_ip」「fcomp32_ip」「fmul32_ip」「fsub32_ip」のそれぞれについて、右クリックして「Re-customize IP」で設定を変更します。
変更点は「DSP48E1」をフルに使用する設定にすることと、「Latency」を2にセットすることです。(現時点の仕様ではLatencyが小さいほど速くなりますが、1以下では正常に動作しませんでした。)
この設定で合成するとデフォルト設定の場合よりも約2.4倍速くなりました。
設定を変更したら再度項目を選択して右クリック、「Generate Output Products」でIPを再生成し、出力された「〜.ngc」と「〜.v」ファイルをコピーします。(詳しくは前回の「SynthesijerライブラリIPの生成」を参照してください。)
今回のIPの演算部ではこれら4つの浮動小数点演算IPしか使用していませんが、独自のプログラムを作成した場合は他のIPの調整も必要になるでしょう。
どのIPが実際に使用されるかは、Javaプログラムを変換してできたVerilog HDLファイルの最後の方を見ると分かります。

ビットストリームの生成・転送

前回と同様に、「system_i-system(system.xmp)」を「Reset Output Products」してからビットストリームを生成、Parallellaに転送します。

ドライバのコンパイル・実行

ドライバのプログラムは synthesijer_template_v1_00_a/devl/driver/synthesijer_template_driver_mandelbrot.tar.gz に入っています。これも前回と同様の方法でParallellaに転送してmake、実行します。

ソースコード

これらのソースコードはBSD 2-Clauseライセンスで公開します。

Template.java : コントロール、描画クラス

8つのインスタンスの演算をスタートすると同時に、一つ前の演算結果を読み出して描画を行い、描画と演算がパイプラインで並列に行われるようにしています。
スレッドの同期は2つのフラグ(req, ack)を使ってハンドシェーク方式で行っています。

/*
  Copyright (c) 2015, miya
  All rights reserved.

  Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:

  1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.

  2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.

  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
  IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
  PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/


public class Template extends Thread
{
  // CONSTANTS
  private static final int PARALLEL = 8;
  private static final int WIDTH = 512;
  private static final int HEIGHT = 512;
  private static final int INT_SIZE = 4;
  private static final int HEIGHT_PLUS_PARALLEL = 520;
  private static final float PARALLEL_F = 8.0f;

  // AXI IO
  public int waddr;
  public int wdata;
  public int wburst;
  public boolean wreq;
  public boolean wbusy;
  public int raddr;
  public int rdata;
  public boolean rreq;
  public boolean rbusy;

  // user IO
  public int gpio0; // fb_addr
  public int gpio1; // line_bytes
  public int gpio2; // mode
  public int gpio3; // param_addr

  private int fb_addr;
  private int line_bytes;
  private int page0;
  private int page1;
  private float cx;
  private float cy;
  private float dx;
  private float speed;
  private int mode;
  private int param_addr;

  private Mandel mandel0 = new Mandel();
  private Mandel mandel1 = new Mandel();
  private Mandel mandel2 = new Mandel();
  private Mandel mandel3 = new Mandel();
  private Mandel mandel4 = new Mandel();
  private Mandel mandel5 = new Mandel();
  private Mandel mandel6 = new Mandel();
  private Mandel mandel7 = new Mandel();

  public void init()
  {
    wreq = false;
    wbusy = false;
    rreq = false;
    rbusy = false;
    fb_addr = gpio0;
    line_bytes = gpio1;
    mode = gpio2;
    param_addr = gpio3;
    int initmode = mode & 0xff;
    if (initmode == 0)
    {
      cx = fixed_to_float(read_data(param_addr));
      cy = fixed_to_float(read_data(param_addr + 4));
      dx = fixed_to_float(read_data(param_addr + 8));
      speed = fixed_to_float(read_data(param_addr + 12));

      mandel0.cx = cx;
      mandel1.cx = cx;
      mandel2.cx = cx;
      mandel3.cx = cx;
      mandel4.cx = cx;
      mandel5.cx = cx;
      mandel6.cx = cx;
      mandel7.cx = cx;

      mandel0.cy = cy;
      mandel1.cy = cy;
      mandel2.cy = cy;
      mandel3.cy = cy;
      mandel4.cy = cy;
      mandel5.cy = cy;
      mandel6.cy = cy;
      mandel7.cy = cy;

      mandel0.dx = dx;
      mandel1.dx = dx;
      mandel2.dx = dx;
      mandel3.dx = dx;
      mandel4.dx = dx;
      mandel5.dx = dx;
      mandel6.dx = dx;
      mandel7.dx = dx;

      mandel0.mode = mode;
      mandel1.mode = mode;
      mandel2.mode = mode;
      mandel3.mode = mode;
      mandel4.mode = mode;
      mandel5.mode = mode;
      mandel6.mode = mode;
      mandel7.mode = mode;

      mandel0.enable = true;
      mandel1.enable = true;
      mandel2.enable = true;
      mandel3.enable = true;
      mandel4.enable = true;
      mandel5.enable = true;
      mandel6.enable = true;
      mandel7.enable = true;

      mandel0.req = false;
      mandel1.req = false;
      mandel2.req = false;
      mandel3.req = false;
      mandel4.req = false;
      mandel5.req = false;
      mandel6.req = false;
      mandel7.req = false;

      mandel0.ack = false;
      mandel1.ack = false;
      mandel2.ack = false;
      mandel3.ack = false;
      mandel4.ack = false;
      mandel5.ack = false;
      mandel6.ack = false;
      mandel7.ack = false;

      mandel0.start();
      mandel1.start();
      mandel2.start();
      mandel3.start();
      mandel4.start();
      mandel5.start();
      mandel6.start();
      mandel7.start();
    }
    else if (initmode == 1)
    {
      mandel0.req = false;
      mandel1.req = false;
      mandel2.req = false;
      mandel3.req = false;
      mandel4.req = false;
      mandel5.req = false;
      mandel6.req = false;
      mandel7.req = false;

      mandel0.enable = false;
      mandel1.enable = false;
      mandel2.enable = false;
      mandel3.enable = false;
      mandel4.enable = false;
      mandel5.enable = false;
      mandel6.enable = false;
      mandel7.enable = false;

      try
      {
        mandel0.join();
        mandel1.join();
        mandel2.join();
        mandel3.join();
        mandel4.join();
        mandel5.join();
        mandel6.join();
        mandel7.join();
      }
      catch (InterruptedException e)
      {
      }
    }
  }

  private float fixed_to_float(int fixed)
  {
    // fixed point format
    // integer:9bits decimal:23bits
    // int fixed = (int)(float * 8388608.0f);
    return (float)fixed * 1.19209289551e-07f;
  }

  private void shortwait()
  {
  }

  private void write_data(int addr, int data, int burst)
  {
    while(wbusy == true)
    {
      yield();
    }
    waddr = addr;
    wdata = data;
    wburst = burst;
    wreq = true;
    if (wreq == true)
    {
      wreq = false;
    }
  }

  private void write_flush()
  {
    while(wbusy == true)
    {
      yield();
    }
  }

  private int read_data(int addr)
  {
    raddr = addr;
    rreq = true;
    if (rreq == true)
    {
      rreq = false;
    }
    shortwait();
    while(rbusy == true)
    {
      yield();
    }
    return rdata;
  }

  public void run()
  {
    page0 = 0;
    page1 = 1;
    float sy = 0.0f;
    int addr = fb_addr;
    int line_bytes_p = line_bytes * PARALLEL;

    mandel0.dx = dx;
    mandel1.dx = dx;
    mandel2.dx = dx;
    mandel3.dx = dx;
    mandel4.dx = dx;
    mandel5.dx = dx;
    mandel6.dx = dx;
    mandel7.dx = dx;

    for (int i = 0; i < HEIGHT_PLUS_PARALLEL; i += PARALLEL)
    {
      mandel0.sy = sy;
      mandel0.page = page0;
      mandel1.sy = sy + 1.0f;
      mandel1.page = page0;
      mandel2.sy = sy + 2.0f;
      mandel2.page = page0;
      mandel3.sy = sy + 3.0f;
      mandel3.page = page0;
      mandel4.sy = sy + 4.0f;
      mandel4.page = page0;
      mandel5.sy = sy + 5.0f;
      mandel5.page = page0;
      mandel6.sy = sy + 6.0f;
      mandel6.page = page0;
      mandel7.sy = sy + 7.0f;
      mandel7.page = page0;

      if (i < HEIGHT)
      {
        // job start
        mandel0.req = true;
        mandel1.req = true;
        mandel2.req = true;
        mandel3.req = true;
        mandel4.req = true;
        mandel5.req = true;
        mandel6.req = true;
        mandel7.req = true;
      }

      if (i >= PARALLEL)
      {
        // draw
        int addr1 = addr;
        int i_s, i_e;
        if (page1 == 0)
        {
          i_s = 0;
        }
        else
        {
          i_s = WIDTH;
        }
        i_e = i_s + WIDTH;
        for (int j = i_s; j < i_e; j++)
        {
          int addr0 = addr1;
          write_data(addr0, mandel0.data[j], 0);
          addr0 += line_bytes;
          write_data(addr0, mandel1.data[j], 0);
          addr0 += line_bytes;
          write_data(addr0, mandel2.data[j], 0);
          addr0 += line_bytes;
          write_data(addr0, mandel3.data[j], 0);
          addr0 += line_bytes;
          write_data(addr0, mandel4.data[j], 0);
          addr0 += line_bytes;
          write_data(addr0, mandel5.data[j], 0);
          addr0 += line_bytes;
          write_data(addr0, mandel6.data[j], 0);
          addr0 += line_bytes;
          write_data(addr0, mandel7.data[j], 0);

          addr1 += INT_SIZE;
        }
        addr += line_bytes_p;
      }

      if (i < HEIGHT)
      {
        // job join
        while ((mandel0.ack == false) ||
               (mandel1.ack == false) ||
               (mandel2.ack == false) ||
               (mandel3.ack == false) ||
               (mandel4.ack == false) ||
               (mandel5.ack == false) ||
               (mandel6.ack == false) ||
               (mandel7.ack == false))
        {
          yield();
        }

        mandel0.req = false;
        mandel1.req = false;
        mandel2.req = false;
        mandel3.req = false;
        mandel4.req = false;
        mandel5.req = false;
        mandel6.req = false;
        mandel7.req = false;

        while ((mandel0.ack == true) ||
               (mandel1.ack == true) ||
               (mandel2.ack == true) ||
               (mandel3.ack == true) ||
               (mandel4.ack == true) ||
               (mandel5.ack == true) ||
               (mandel6.ack == true) ||
               (mandel7.ack == true))
        {
          yield();
        }
      }

      int p = page0;
      page0 = page1;
      page1 = p;
      sy += PARALLEL_F;
    }
    dx *= speed;
  }
}

Mandel.java : マンデルブロ集合演算クラス

演算クラスには横1ライン分(512個)の配列を2ページ分持たせ、演算の裏側で描画スレッドから演算結果を読み出せるようにしています。各インスタンスが完全に独立したワーク用メモリを持つことになるため、排他制御等で演算が妨げられることなく、理想的な並列性が得られます。

/*
  Copyright (c) 2015, miya
  All rights reserved.

  Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:

  1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.

  2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.

  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
  IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
  PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/


public class Mandel extends Thread
{
  private static final float HALF_WIDTH = 256.0f;
  private static final int WIDTH = 512;
  private static final float MAX_C = 4.0f;

  public float cx;
  public float cy;
  public float sy;
  public float dx;
  public int page;
  public final int[] data = new int[1024 /*BUF_SIZE*/];
  public boolean req;
  public boolean ack;
  public boolean enable;
  public int mode;

  public void run()
  {
    while (true)
    {
      while ((req == false) && (enable == true))
      {
        yield();
      }

      if (enable == false)
      {
        break;
      }

      float x, y, calc_size;
      calc_size = dx * HALF_WIDTH;
      x = cx - calc_size;
      y = cy - calc_size + dx * sy;
      int max_count = (mode >> 8) & 0xff;

      int i_s, i_e;
      if (page == 0)
      {
        i_s = 0;
      }
      else
      {
        i_s = WIDTH;
      }
      i_e = i_s + WIDTH;

      for (int i = i_s; i < i_e; i++)
      {
        float a, b, a2, b2, c;
        a = 0.0f;
        b = 0.0f;
        a2 = 0.0f;
        b2 = 0.0f;
        c = 0.0f;
        int count = max_count;

        while ((c < MAX_C) && (count > 0))
        {
          b = a * b * 2.0f - y;
          a = a2 - b2 - x;
          a2 = a * a;
          b2 = b * b;
          c = a2 + b2;
          count--;
        }

        int color = 0;
        color = 0xff000000 | ((count << 3) + (count << 10) + (count << 18));
        data[i] = color;
        x += dx;
      }
      ack = true;

      while (req == true)
      {
        yield();
      }

      ack = false;
    }
  }
}

main.c : ドライバプログラム
/*
  Copyright (c) 2015, miya
  All rights reserved.

  Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:

  1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.

  2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.

  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
  IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
  PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/


#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <sys/ioctl.h>
#include <sys/time.h>
#include <linux/fb.h>
#include <stdint.h>

#define DEVMEM "/dev/mem"
#define DEVFB "/dev/fb0"
#define BASE_ADDR 0x60000000
#define MAP_SIZE 0x1000
#define BPP 4
#define LOOP 120

static inline long long get_tick_count(void)
{
  struct timeval timeprof;
  gettimeofday(&timeprof, NULL);
  return ((long long)timeprof.tv_sec * (long long)1000000 + (long long)timeprof.tv_usec);
}

static inline uint32_t pack_control(uint32_t addr, uint32_t we, uint32_t req)
{
  return ((addr & 0xff) << 24) | ((we & 0x1) << 1) | (req & 0x1);
}

static inline void write_reg(void * mem, uint32_t addr, uint32_t data)
{
  volatile uint32_t * reg = mem;
  reg[1] = data;
  reg[0] = pack_control(addr, 1, 1);
  while (reg[2] == 0)
  {
    usleep(1);
  }
  reg[0] = pack_control(addr, 1, 0);
  while (reg[2] == 1)
  {
    usleep(1);
  }
}

static inline uint32_t read_reg(void * mem, uint32_t addr)
{
  volatile uint32_t * reg = mem;
  reg[0] = pack_control(addr, 0, 1);
  while (reg[2] == 0)
  {
    usleep(1);
  }
  reg[0] = pack_control(addr, 0, 0);
  while (reg[2] == 1)
  {
    usleep(1);
  }
  return reg[3];
}

int main(int argc, char *argv[])
{
  unsigned long fb_addr = 0;
  uint32_t line_length = 0;
  uint32_t width = 0;
  uint32_t height = 0;

  int fdfb = open(DEVFB, O_RDWR);
  if (fdfb > 0)
  {
    struct fb_fix_screeninfo fbf;
    struct fb_var_screeninfo fbv;
    if (ioctl(fdfb, FBIOGET_FSCREENINFO, &fbf) == 0)
    {
      fb_addr = fbf.smem_start;
      line_length = fbf.line_length;
    }
    if (ioctl(fdfb, FBIOGET_VSCREENINFO, &fbv) == 0)
    {
      width = fbv.xres;
      height = fbv.yres;
    }
    close(fdfb);
  }

  int fd = open(DEVMEM, O_RDWR | O_SYNC);
  if (fd == -1)
  {
    perror("cannot open /dev/mem");
    abort();
  }

  uint32_t *mem = mmap(NULL, MAP_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, (off_t)BASE_ADDR);

  if (mem == MAP_FAILED)
  {
    perror("map failed");
    abort();
  }

  uint32_t *fbmem = mmap(NULL, line_length * height, PROT_READ | PROT_WRITE, MAP_SHARED, fd, (off_t)fb_addr);

  if (fbmem == MAP_FAILED)
  {
    perror("map failed");
    abort();
  }

  uint32_t windowsize = 512;
  if ((windowsize > height) || (windowsize > width))
  {
    perror("screen size");
    abort();
  }
  uint32_t sy = (height - windowsize) / 2;
  uint32_t sx = (width - windowsize) / 2;
  uint32_t saddr = sy * line_length + (sx * BPP);

  // for benchmark (LOOP = 10)
/*
  fbmem[0] = (int)(0.7509765625f * 8388608.0f);
  fbmem[1] = (int)(0.0f * 8388608.0f);
  fbmem[2] = (int)(0.25f / 512.0f * 8388608.0f);
  fbmem[3] = (int)(246.0f / 256.0f * 8388608.0f);
*/


  fbmem[0] = (int)(1.49f * 8388608.0f);
  fbmem[1] = (int)(0.0f * 8388608.0f);
  fbmem[2] = (int)(4.0f / 512.0f * 8388608.0f);
  fbmem[3] = (int)(246.0f / 256.0f * 8388608.0f);

  long long start_tick = get_tick_count();

  // Template.java: init
  write_reg(mem, 1, fb_addr + saddr);
  write_reg(mem, 2, line_length);
  write_reg(mem, 3, (128 << 8) + 0);
  write_reg(mem, 4, fb_addr);
  write_reg(mem, 0, 0);

  int i;
  for (i = 0; i < LOOP; i++)
  {
    // Template.java: run()
    write_reg(mem, 0, 1);
  }

  write_reg(mem, 3, (128 << 8) + 1);
  write_reg(mem, 0, 0);

  float calctime = (float)(get_tick_count() - start_tick) / 1000000.0f;
  float fps = (float)LOOP / calctime;
  printf("time: %f sec\n", calctime);
  printf("fps: %f\n", fps);

  munmap(mem, MAP_SIZE);
  close(fd);
  return 0;
}