Parallella Fan!
Home記事一覧BBS

SynthesijerでJavaプログラムからHDLコードを自動生成する

【更新履歴】
2015/04/27 公式セットアップスクリプトに準拠する形に修正しました。以前にダウンロードされた方は再度ダウンロードしなおしてください。
2015/03/29 プロジェクトのMakefileを修正しました。以前にダウンロードされた方は再度ダウンロードしなおしてください。
2015/03/11 通常のJavaで実行しやすくするため Template.java をThreadクラスに変更。
2015/02/19 浮動小数点演算等のSynthesijerのライブラリIPを使用するように修正、また、ライブラリIPの生成方法を追加
2015/02/14 新規公開

Synthesijerはオープンソースの高位合成ツールです。通常のJavaプログラムからHDLコードを自動生成できます。今回、Javaで書いたライフゲームのプログラムをVerilog HDLコードに変換してParallella上のFPGAに実装してみました。

これは実際にParallellaで実行している様子です。
1280x720のフレームバッファに描画しています。

ライフゲームのセルの計算や描画ルーチンは普通のJavaプログラムで書いたものをVerilog HDLコードに変換しています。メモリ(フレームバッファ)の読み書きを行うためのAXI MasterインターフェースとZynqのCPUと連携するためのAXI Slaveインターフェース部分などはVerilog HDLで手作業で記述しました。
インターフェースは汎用的に使えるように作っているので、このプロジェクトをテンプレートにしてJavaプログラム部分のみ書き換えて、様々なアプリケーションをFPGAに実装することができます。

Synthesijerのインストール

まず、「ZEROからのFPGA:高位合成ツール「Synthesijer」を使う」の方法でSynthesijerをダウンロード、セットアップしてください。

プロジェクトの作成

「FPGAに独自の回路を追加する」と同様に、プロジェクトを作成してIPを追加していきます。始めての方はまずこのチュートリアルを済ませてください。そして、「synthesijer_template」という名前で新しいプロジェクト(HDMI対応版)を作成して、IPを追加する直前の状態まで完了させてください。以下では今回のIPを追加する際の相違点のみ解説します。

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

端末で、

cd ~/parallella_hw_projects/user_ip/edk/pcores

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

tar xzf synthesijer_template_v1_00_a.tar.gz

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

cd synthesijer_template_v1_00_a/devl/synthesijer

make

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

cp ~/synthesijer/synthesijer_lib*/verilog/*.v ~/parallella_hw_projects/user_ip/edk/pcores/synthesijer_template_v1_00_a/hdl/verilog/

SynthesijerライブラリIPの生成

プロジェクトにSynthesijerライブラリIPを追加します。


PlanAheadで
Project Manager→Add Sourcesボタンをクリックします。


Add Existing IPを選択して「Next」ボタンをクリックします。


「Add Directories」ボタンをクリックします。


~/synthesijer/synthesijer_lib*/Xilinx/Spartan6 を「Select」します。


[v]Copy sources into projectにチェックして「Finish」をクリックします。


Sourcesウィンドウに追加されたxxxxx_ip(xxxxx_ip.xci)の項目をCtrl+マウスクリックまたはShift+マウスクリックを使って全部選択した状態にして、その選択された部分を右クリックして「Generate Output Products」を実行します。(これにはかなり時間がかかります。)


「OK」をクリックします。

これらのIPの生成ファイルは ~/parallella_hw_projects/synthesijer_template/synthesijer_template.srcs/sources_1/ip/IP名 ディレクトリに生成されます。

それぞれのフォルダの中の IP名.ngc を ~/parallella_hw_projects/user_ip/edk/pcores/synthesijer_template_v1_00_a/netlist にコピーします。

cd ~/parallella_hw_projects/synthesijer_template/synthesijer_template.srcs/sources_1/ip

cp */*.ngc ~/parallella_hw_projects/user_ip/edk/pcores/synthesijer_template_v1_00_a/netlist/

また、 IP名.v を ~/parallella_hw_projects/user_ip/edk/pcores/synthesijer_template_v1_00_a/hdl/verilog にコピーします。

cp */*.v ~/parallella_hw_projects/user_ip/edk/pcores/synthesijer_template_v1_00_a/hdl/verilog/

IPの追加と接続

XPSで一度「Rescan User IP Repositories」を行った後、「IP Catalog」に追加された「SYNTHESIJER TEMPLATE」という名前のIPを「Add IP」します。
IPのコンフィギュレーションはデフォルトのままでOKです。

「User will make necessary connections and settings」を選択して手動でIPの接続を行います。

「Bus Interfaces」の接続では、以下のバスを繋ぎます。

synthesijer_template_0:M_AXI --1-- processing_system7_0:S_AXI_HP0

synthesijer_template_0:S_AXI --2-- processing_system7_0:M_AXI_GP0

processing_system7_0:S_AXI_HPx は、FPGA側をマスターとしてSoC側のメモリ等に高速(高帯域)にアクセスできるポートです。

この接続を簡単に行うためには、左側の接続線が描かれているパネルで以下のポイント(マウスカーソルのある場所)をクリックしていきます。







接続されると以下のような状態になります。



次は「Ports」の接続です。ここではクロックのポートを接続します。このIPのM_AXIはaxi_interconnect2を経由しているので、そのバスクロックであるprocessing_system7_0::FCLK_CLK1を繋ぎ、S_AXIはaxi_interconnect1を経由しているので、processing_system7_0::FCLK_CLK0を繋ぎます。



「Addresses」は Base Address:0x60000000、Size:64K、Lock:[X]と設定します。



次に「Perform Project Design Rule Checks」ボタンをクリックしてIPの接続にエラーがないか確認します。

これでIPの接続は完了です。XPSをExitしてPlanAheadに戻ります。

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

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

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

ドライバのプログラムは synthesijer_template_v1_00_a/devl/driver/synthesijer_template_driver.tar.gz に入っています。これも前回と同様の方法でParallellaに転送してmake、実行します。
実行の際、コンソールモードに切り替える必要があります。

sudo chvt 2

ローカルで作業している場合はその後ログインしてドライバプログラムを実行します。

Xに戻るには

sudo chvt 7

ソースコード

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

Template.java : ライフゲームJavaプログラム本体
/*
  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
{
  // 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; // dot_size
  public int gpio3;

  private int fb_addr;
  private int line_bytes;
  private int dot_size;
  private int calc_page;
  private int draw_page;

  private byte cell[] = new byte[8192 /*all_page_size*/];


  public void init()
  {
    wreq = false;
    wbusy = false;
    rreq = false;
    rbusy = false;
    fb_addr = gpio0;
    line_bytes = gpio1;
    dot_size = gpio2;
    for (int i = 0; i < 8192 /*all_page_size*/; i++)
    {
      cell[i] = (byte)0;
    }
    set_cell(34, 29, 0, (byte)1);
    set_cell(32, 30, 0, (byte)1);
    set_cell(34, 30, 0, (byte)1);
    set_cell(35, 30, 0, (byte)1);
    set_cell(32, 31, 0, (byte)1);
    set_cell(34, 31, 0, (byte)1);
    set_cell(32, 32, 0, (byte)1);
    set_cell(30, 33, 0, (byte)1);
    set_cell(28, 34, 0, (byte)1);
    set_cell(30, 34, 0, (byte)1);
    calc_page = 1;
    draw_page = 0;
    clear_screen();
    draw_cells();
    calc_page = 0;
    draw_page = 1;
  }

  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;
    wreq = false;
  }

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

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

  private void fill_rect(int addr, int data, int width, int height)
  {
    int len;
    if (width < 16 /*max_burst*/)
    {
      len = width;
    }
    else
    {
      len = 16 /*max_burst*/;
    }
    int burst = len - 1;
    int w = width >> 4 /*max_burst_bits*/;
    int dx = len << 2 /*bpp_bits*/;
    int ady = addr;
    for (int y = 0; y < height; y++)
    {
      int adx = ady;
      for (int x = 0; x < w; x++)
      {
        write_data(adx, data, burst);
        adx += dx;
      }
      ady += line_bytes;
    }
    int left_start = w << 4 /*max_burst_bits*/;
    int left_size = width - left_start;
    if (left_size > 0)
    {
      int left_burst = left_size - 1;
      ady = addr + (left_start << 2 /*bpp_bits*/);
      for (int y = 0; y < height; y++)
      {
        write_data(ady, data, left_burst);
        ady += line_bytes;
      }
    }
  }

  private int get_index(int x, int y, int page)
  {
    return (x & 63 /*cell_size_minus_1*/) + ((y & 63 /*cell_size_minus_1*/) << 6 /*cell_size_bits*/) + (page << 12 /*page_bits*/);
  }

  private byte get_cell(int x, int y, int page)
  {
    return cell[get_index(x, y, page)];
  }

  private void set_cell(int x, int y, int page, byte value)
  {
    cell[get_index(x, y, page)] = value;
  }

  private int get_neighbor(int x, int y, int p)
  {
    int neighbor =
      get_cell(x-1,y-1,p)+get_cell(x,y-1,p)+get_cell(x+1,y-1,p)+
      get_cell(x-1,y,p)+                    get_cell(x+1,y,p)+
      get_cell(x-1,y+1,p)+get_cell(x,y+1,p)+get_cell(x+1,y+1,p);
    return neighbor;
  }

  private void calc_cells()
  {
    for (int y = 0; y < 64 /*cell_size*/; y++)
    {
      for (int x = 0; x < 64 /*cell_size*/; x++)
      {
        byte me = get_cell(x, y, calc_page);
        set_cell(x, y, draw_page, me);
        int neighbor = get_neighbor(x, y , calc_page);
        if (me == (byte)0)
        {
          if (neighbor == 3)
          {
            set_cell(x, y, draw_page, (byte)1);
          }
        }
        else
        {
          if ((neighbor < 2) || (neighbor > 3))
          {
            set_cell(x, y, draw_page, (byte)0);
          }
        }
      }
    }
  }

  private void clear_screen()
  {
    int size = dot_size << 6 /*cell_size_bits*/;
    fill_rect(fb_addr, 0xff000000 /*off_color*/, size, size);
  }

  private void draw_cells()
  {
    int width = dot_size;
    int height = dot_size;
    int dx = width << 2 /*bpp_bits*/;
    int dy = 0;
    for (int i = 0; i < height; i++)
    {
      dy += line_bytes;
    }
    int fby = fb_addr;
    for (int y = 0; y < 64 /*cell_size*/; y++)
    {
      int fbx = fby;
      for (int x = 0; x < 64 /*cell_size*/; x++)
      {
        int cd = get_cell(x, y, draw_page);
        int cc = get_cell(x, y, calc_page);
        if (cd != cc)
        {
          if (cd == (byte)1)
          {
            fill_rect(fbx, 0xffffffff /*on_color*/, width, height);
          }
          else
          {
            fill_rect(fbx, 0xff000000 /*off_color*/, width, height);
          }
        }
        fbx += dx;
      }
      fby += dy;
    }
  }

  public void run()
  {
    calc_cells();
    draw_cells();
    int p = calc_page;
    calc_page = draw_page;
    draw_page = p;
  }
}

synthesijer_template.v : AXIインターフェースのVerilog HDLコード
/*
  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.
*/


module synthesijer_template
  #(
    parameter C_S_AXI_DATA_WIDTH = 32'd32,
    parameter C_BASEADDR = 32'hFFFFFFFF,
    parameter C_HIGHADDR = 32'h00000000,
    parameter C_M_AXI_DATA_WIDTH = 32'd32,
    parameter C_M_STRB_WIDTH = 32'd4,
    parameter C_M_AXI_THREAD_ID_WIDTH = 32'd1,
    parameter C_DATA_WIDTH_B = 32'd2
    )
  (
   input                                  S_AXI_ACLK,
   input                                  S_AXI_ARESETN,
   input [31:0]                           S_AXI_AWADDR,
   input [2:0]                            S_AXI_AWPROT,
   input                                  S_AXI_AWVALID,
   output                                 S_AXI_AWREADY,
   input [31:0]                           S_AXI_WDATA,
   input [3:0]                            S_AXI_WSTRB,
   input                                  S_AXI_WVALID,
   output                                 S_AXI_WREADY,
   output [1:0]                           S_AXI_BRESP,
   output                                 S_AXI_BVALID,
   input                                  S_AXI_BREADY,
   input [31:0]                           S_AXI_ARADDR,
   input [2:0]                            S_AXI_ARPROT,
   input                                  S_AXI_ARVALID,
   output                                 S_AXI_ARREADY,
   output [31:0]                          S_AXI_RDATA,
   output [1:0]                           S_AXI_RRESP,
   output                                 S_AXI_RVALID,
   input                                  S_AXI_RREADY,
   input                                  M_AXI_ACLK,
   input                                  M_AXI_ARESETN,
   output [C_M_AXI_THREAD_ID_WIDTH - 1:0] M_AXI_AWID,
   output [31:0]                          M_AXI_AWADDR,
   output [7:0]                           M_AXI_AWLEN,
   output [2:0]                           M_AXI_AWSIZE,
   output [1:0]                           M_AXI_AWBURST,
   output [1:0]                           M_AXI_AWLOCK,
   output [3:0]                           M_AXI_AWCACHE,
   output [2:0]                           M_AXI_AWPROT,
   output                                 M_AXI_AWVALID,
   input                                  M_AXI_AWREADY,
   output [C_M_AXI_DATA_WIDTH - 1:0]      M_AXI_WDATA,
   output [C_M_STRB_WIDTH - 1:0]          M_AXI_WSTRB,
   output                                 M_AXI_WLAST,
   output                                 M_AXI_WVALID,
   input                                  M_AXI_WREADY,
   input [C_M_AXI_THREAD_ID_WIDTH - 1:0]  M_AXI_BID,
   input [1:0]                            M_AXI_BRESP,
   input                                  M_AXI_BVALID,
   output                                 M_AXI_BREADY,
   output [C_M_AXI_THREAD_ID_WIDTH - 1:0] M_AXI_ARID,
   output [31:0]                          M_AXI_ARADDR,
   output [7:0]                           M_AXI_ARLEN,
   output [2:0]                           M_AXI_ARSIZE,
   output [1:0]                           M_AXI_ARBURST,
   output [1:0]                           M_AXI_ARLOCK,
   output [3:0]                           M_AXI_ARCACHE,
   output [2:0]                           M_AXI_ARPROT,
   output                                 M_AXI_ARVALID,
   input                                  M_AXI_ARREADY,
   input [C_M_AXI_THREAD_ID_WIDTH - 1:0]  M_AXI_RID,
   input [C_M_AXI_DATA_WIDTH - 1:0]       M_AXI_RDATA,
   input [1:0]                            M_AXI_RRESP,
   input                                  M_AXI_RLAST,
   input                                  M_AXI_RVALID,
   output                                 M_AXI_RREADY
   );

  parameter C_STATE_S_ADDR = 3'd0;
  parameter C_STATE_S_W_DATA = 3'd1;
  parameter C_STATE_S_W_RESPONSE = 3'd2;
  parameter C_STATE_S_R_DATA = 3'd3;
  parameter C_STATE_S_R_READY = 3'd4;
  parameter C_S_ADDR_MASK_LSB = 32'd16;

  parameter C_STATE_M_W_IDLE = 3'd0;
  parameter C_STATE_M_W_ADDR = 3'd1;
  parameter C_STATE_M_W_DATA = 3'd2;
  parameter C_STATE_M_W_RESPONSE = 3'd3;

  parameter C_STATE_M_R_IDLE = 3'd0;
  parameter C_STATE_M_R_ADDR = 3'd1;
  parameter C_STATE_M_R_DATA = 3'd2;

  parameter C_STATE_M_C_IDLE = 3'd0;
  parameter C_STATE_M_C_INIT1 = 3'd1;
  parameter C_STATE_M_C_INIT2 = 3'd2;
  parameter C_STATE_M_C_RUN1 = 3'd3;
  parameter C_STATE_M_C_RUN2 = 3'd4;
  parameter C_STATE_M_C_PARAM_W = 3'd5;
  parameter C_STATE_M_C_PARAM_R = 3'd6;
  parameter C_STATE_M_C_FINISH = 3'd7;

  parameter C_M_C_ADDR_GO = 3'd0;
  parameter C_M_C_ADDR_GPIO0 = 3'd1;
  parameter C_M_C_ADDR_GPIO1 = 3'd2;
  parameter C_M_C_ADDR_GPIO2 = 3'd3;
  parameter C_M_C_ADDR_GPIO3 = 3'd4;

  parameter C_SYNC_ADDR_MSB = 7;
  parameter C_BUF_LEN_IN_BITS = 32'd10;
  parameter C_BUF_BYTES_IN_BITS = 32'd2;
  parameter C_BUF_MSB = (32'd1 << (C_BUF_BYTES_IN_BITS + 32'd3)) - 32'd1;

  // output port registers
  reg                                     s_axi_awready;
  reg                                     s_axi_wready;
  reg [1:0]                               s_axi_bresp;
  reg                                     s_axi_bvalid;
  reg                                     s_axi_arready;
  reg [31:0]                              s_axi_rdata;
  reg [1:0]                               s_axi_rresp;
  reg                                     s_axi_rvalid;
  reg [C_M_AXI_THREAD_ID_WIDTH - 1:0]     m_axi_awid;
  reg [31:0]                              m_axi_awaddr;
  reg [7:0]                               m_axi_awlen;
  reg [1:0]                               m_axi_awburst;
  reg [1:0]                               m_axi_awlock;
  reg [3:0]                               m_axi_awcache;
  reg [2:0]                               m_axi_awprot;
  reg                                     m_axi_awvalid;
  reg [C_M_AXI_DATA_WIDTH - 1:0]          m_axi_wdata;
  reg                                     m_axi_wvalid;
  reg [C_M_STRB_WIDTH - 1:0]              m_axi_wstrb;
  reg                                     m_axi_bready;
  reg [C_M_AXI_THREAD_ID_WIDTH - 1:0]     m_axi_arid;
  reg [31:0]                              m_axi_araddr;
  reg [7:0]                               m_axi_arlen;
  reg [1:0]                               m_axi_arburst;
  reg [1:0]                               m_axi_arlock;
  reg [3:0]                               m_axi_arcache;
  reg [2:0]                               m_axi_arprot;
  reg                                     m_axi_arvalid;
  reg                                     m_axi_rready;

  // user registers
  reg [2:0]                               state_s;
  reg [31:0]                              s_address;

  // axi master controller
  reg [2:0]                               state_m_c;
  reg [7:0]                               m_c_address;
  reg                                     m_c_we;

  // axi master
  reg [2:0]                               state_m_w;
  reg [2:0]                               state_m_r;
  reg [7:0]                               m_w_burst_count;

  // for cdc_synchronizer
  reg                                     sync_a_req;
  reg [C_SYNC_ADDR_MSB:0]                 sync_a_addr;
  reg [31:0]                              sync_a_data;
  reg                                     sync_a_we;
  reg                                     sync_b_ack;
  reg [31:0]                              sync_b_data;
  wire                                    sync_a2b_req;
  wire [C_SYNC_ADDR_MSB:0]                sync_a2b_addr;
  wire [31:0]                             sync_a2b_data;
  wire                                    sync_a2b_we;
  wire [31:0]                             sync_b2a_data;
  wire                                    sync_b2a_ack;

  // copied from Top.v (1)
  // -- begin --
  wire  class_obj_0000_clk;
  wire  class_obj_0000_reset;
  reg signed [32-1 : 0] class_obj_0000_waddr_in = 0;
  reg  class_obj_0000_waddr_we = 1'b0;
  wire signed [32-1 : 0] class_obj_0000_waddr_out;
  reg signed [32-1 : 0] class_obj_0000_wdata_in = 0;
  reg  class_obj_0000_wdata_we = 1'b0;
  wire signed [32-1 : 0] class_obj_0000_wdata_out;
  reg signed [32-1 : 0] class_obj_0000_wburst_in = 0;
  reg  class_obj_0000_wburst_we = 1'b0;
  wire signed [32-1 : 0] class_obj_0000_wburst_out;
  reg  class_obj_0000_wreq_in = 1'b0;
  reg  class_obj_0000_wreq_we = 1'b0;
  wire  class_obj_0000_wreq_out;
  reg  class_obj_0000_wbusy_in = 1'b0;
  reg  class_obj_0000_wbusy_we = 1'b0;
  wire  class_obj_0000_wbusy_out;
  reg signed [32-1 : 0] class_obj_0000_raddr_in = 0;
  reg  class_obj_0000_raddr_we = 1'b0;
  wire signed [32-1 : 0] class_obj_0000_raddr_out;
  reg signed [32-1 : 0] class_obj_0000_rdata_in = 0;
  reg  class_obj_0000_rdata_we = 1'b0;
  wire signed [32-1 : 0] class_obj_0000_rdata_out;
  reg  class_obj_0000_rreq_in = 1'b0;
  reg  class_obj_0000_rreq_we = 1'b0;
  wire  class_obj_0000_rreq_out;
  reg  class_obj_0000_rbusy_in = 1'b0;
  reg  class_obj_0000_rbusy_we = 1'b0;
  wire  class_obj_0000_rbusy_out;
  reg signed [32-1 : 0] class_obj_0000_gpio0_in = 0;
  reg  class_obj_0000_gpio0_we = 1'b0;
  wire signed [32-1 : 0] class_obj_0000_gpio0_out;
  reg signed [32-1 : 0] class_obj_0000_gpio1_in = 0;
  reg  class_obj_0000_gpio1_we = 1'b0;
  wire signed [32-1 : 0] class_obj_0000_gpio1_out;
  reg signed [32-1 : 0] class_obj_0000_gpio2_in = 0;
  reg  class_obj_0000_gpio2_we = 1'b0;
  wire signed [32-1 : 0] class_obj_0000_gpio2_out;
  reg signed [32-1 : 0] class_obj_0000_gpio3_in = 0;
  reg  class_obj_0000_gpio3_we = 1'b0;
  wire signed [32-1 : 0] class_obj_0000_gpio3_out;
  wire  class_obj_0000_init_busy;
  reg  class_obj_0000_init_req = 1'b0;
  wire  class_obj_0000_run_busy;
  reg  class_obj_0000_run_req = 1'b0;
  wire  class_obj_0000_start_busy;
  reg  class_obj_0000_start_req = 1'b0;
  wire  class_obj_0000_join_busy;
  reg  class_obj_0000_join_req = 1'b0;
  wire  class_obj_0000_yield_busy;
  reg  class_obj_0000_yield_req = 1'b0;
  // -- end --

  // for synthesijer module
  assign class_obj_0000_clk = M_AXI_ACLK;
  assign class_obj_0000_reset = ~M_AXI_ARESETN;

  // output port assignments
  assign S_AXI_AWREADY = s_axi_awready;
  assign S_AXI_WREADY = s_axi_wready;
  assign S_AXI_BRESP = s_axi_bresp;
  assign S_AXI_BVALID = s_axi_bvalid;
  assign S_AXI_ARREADY = s_axi_arready;
  assign S_AXI_RDATA = s_axi_rdata;
  assign S_AXI_RRESP = s_axi_rresp;
  assign S_AXI_RVALID = s_axi_rvalid;
  assign M_AXI_AWID = m_axi_awid;
  assign M_AXI_AWADDR = m_axi_awaddr;
  assign M_AXI_AWLEN = m_axi_awlen;
  assign M_AXI_AWBURST = m_axi_awburst;
  assign M_AXI_AWLOCK = m_axi_awlock;
  assign M_AXI_AWCACHE = m_axi_awcache;
  assign M_AXI_AWPROT = m_axi_awprot;
  assign M_AXI_AWVALID = m_axi_awvalid;
  assign M_AXI_WSTRB = m_axi_wstrb;
  assign M_AXI_BREADY = m_axi_bready;
  assign M_AXI_ARID = m_axi_arid;
  assign M_AXI_ARADDR = m_axi_araddr;
  assign M_AXI_ARLEN = m_axi_arlen;
  assign M_AXI_ARBURST = m_axi_arburst;
  assign M_AXI_ARLOCK = m_axi_arlock;
  assign M_AXI_ARCACHE = m_axi_arcache;
  assign M_AXI_ARPROT = m_axi_arprot;
  assign M_AXI_ARVALID = m_axi_arvalid;
  assign M_AXI_RREADY = m_axi_rready;
  assign M_AXI_WDATA = m_axi_wdata;
  assign M_AXI_WVALID = m_axi_wvalid;
  assign M_AXI_ARSIZE = C_DATA_WIDTH_B;
  assign M_AXI_AWSIZE = C_DATA_WIDTH_B;

  // AXI4-Lite slave interface
  // To write: 1:write data_write, 2:write control with write_enable=1 and req=1, 3:poll until ack==1, 4:write control with write_enable=1 and req=0, 5:poll until ack==0
  // To read: 1:write control with write_enable=0 and req=1, 2:poll until ack==1, 3:write control with write_enable=0 and req=0, 4:poll until ack==0, 5:read data_read
  // addr:0xXXXXXXX0 :(w) control
  // [31:24]:parameter_number, [1]:write_enable(1:write 0:read) [0]:req(1:go 0:idle)
  // addr:0xXXXXXXX4 :(w) data_write
  // [31:0]:data
  // addr:0xXXXXXXX8 :(r) ack
  // [0]:ack 0:normal 1:done & wait for req=0
  // addr:0xXXXXXXXC :(r) data_read
  // [31:0]:data

  // AXI4-Lite slave
  always @(posedge S_AXI_ACLK)
    begin
      if (S_AXI_ARESETN == 1'b0)
        begin
          s_axi_awready <= 1'b0;
          s_axi_wready <= 1'b0;
          s_axi_bvalid <= 1'b0;
          s_axi_arready <= 1'b0;
          s_axi_rvalid <= 1'b0;
          s_axi_bresp <= 2'b00;
          s_axi_rresp <= 2'b00;
          state_s <= C_STATE_S_ADDR;
        end
      else
        begin
          case (state_s)
            C_STATE_S_ADDR:
              begin
                // address decode
                if ((S_AXI_AWVALID == 1'b1) && (S_AXI_AWADDR[31:C_S_ADDR_MASK_LSB] == C_BASEADDR[31:C_S_ADDR_MASK_LSB]))
                  // write: get the address
                  begin
                    s_address <= S_AXI_AWADDR;
                    s_axi_awready <= 1'b1;
                    state_s <= C_STATE_S_W_DATA;
                  end
                else if ((S_AXI_ARVALID == 1'b1) && (S_AXI_ARADDR[31:C_S_ADDR_MASK_LSB] == C_BASEADDR[31:C_S_ADDR_MASK_LSB]))
                  // read: get the address
                  begin
                    s_address <= S_AXI_ARADDR;
                    s_axi_arready <= 1'b1;
                    state_s <= C_STATE_S_R_DATA;
                  end
              end
            // write: get the data
            C_STATE_S_W_DATA:
              if (S_AXI_WVALID == 1'b1)
                begin
                  case (s_address[3:2])
                    2'd0:
                      // addr: 0xXXXXXXX0
                      begin
                        // write control data
                        sync_a_addr <= S_AXI_WDATA[31:24];
                        sync_a_we <= S_AXI_WDATA[1];
                        sync_a_req <= S_AXI_WDATA[0];
                      end
                    2'd1:
                      // addr: 0xXXXXXXX4
                      begin
                        // write data
                        sync_a_data <= S_AXI_WDATA;
                      end
                    default: ;
                  endcase
                  s_axi_awready <= 1'b0;
                  s_axi_wready <= 1'b1;
                  s_axi_bvalid <= 1'b1;
                  state_s <= C_STATE_S_W_RESPONSE;
                end
            // write: wait for bready
            C_STATE_S_W_RESPONSE:
              if (S_AXI_BREADY == 1'b1)
                begin
                  s_axi_wready <= 1'b0;
                  s_axi_bvalid <= 1'b0;
                  state_s <= C_STATE_S_ADDR;
                end
            // read: pass the data
            C_STATE_S_R_DATA:
              begin
                case (s_address[3:2])
                  2'd2:
                    // addr: 0xXXXXXXX8
                    begin
                      // read ack
                      s_axi_rdata <= sync_b2a_ack;
                    end
                  2'd3:
                    // addr: 0xXXXXXXXC
                    begin
                      // read data
                      s_axi_rdata <= sync_b2a_data;
                    end
                  default: ;
                endcase
                s_axi_arready <= 1'b0;
                s_axi_rvalid <= 1'b1;
                state_s <= C_STATE_S_R_READY;
              end
            // read: wait for rready
            C_STATE_S_R_READY:
              if (S_AXI_RREADY == 1'b1)
                begin
                  s_axi_rvalid <= 1'b0;
                  state_s <= C_STATE_S_ADDR;
                end
            default: ;
          endcase
        end
    end

  // ---- state machine A ----
  // 1. to start, if B2A_ACK == low, goto 2 else goto 1
  // 2. set A_ADDR and A_DATA, set A_REQ high
  // 3. poll B2A_ACK, if B2A_ACK == high, set A_REQ low
  // 4. poll B2A_ACK, if B2A_ACK == low, goto 1
  // ---- state machine B ----
  // 1. poll A2B_REQ, if A2B_REQ == high and B_ACK == low, get A_ADDR and A_DATA, set B_ACK high
  // 2. poll A2B_REQ, if A2B_REQ == low, set B_ACK low, goto 1

  // class Template controller
  // reg addr:
  // 0: go command sync_a2b_data[0] 0:init 1:run
  // 1: GPIO0 sync_a2b_data[31:0]
  // 2: GPIO1 sync_a2b_data[31:0]
  // 3: GPIO2 sync_a2b_data[31:0]
  // 4: GPIO3 sync_a2b_data[31:0]
  always @(posedge M_AXI_ACLK)
    begin
      if (M_AXI_ARESETN == 1'b0)
        begin
          sync_b_ack <= 1'b0;
          sync_b_data <= 1'b0;
          class_obj_0000_run_req <= 1'b0;
          state_m_c <= C_STATE_M_C_IDLE;
        end
      else
        begin
          case (state_m_c)
            C_STATE_M_C_IDLE:
              begin
                if (sync_a2b_req == 1'b1)
                  begin
                    m_c_we <= sync_a2b_we;
                    m_c_address <= sync_a2b_addr;
                    if (sync_a2b_we == 1'b1)
                      begin
                        // command / parameter write
                        case (sync_a2b_addr)
                          C_M_C_ADDR_GO:
                            begin
                              case (sync_a2b_data[0])
                                1'b0:
                                  begin
                                    class_obj_0000_init_req <= 1'b1;
                                    state_m_c <= C_STATE_M_C_INIT1;
                                  end
                                1'b1:
                                  begin
                                    class_obj_0000_run_req <= 1'b1;
                                    state_m_c <= C_STATE_M_C_RUN1;
                                  end
                                default: ;
                              endcase
                            end
                          C_M_C_ADDR_GPIO0:
                            begin
                              class_obj_0000_gpio0_in <= sync_a2b_data;
                              class_obj_0000_gpio0_we <= 1'b1;
                              state_m_c <= C_STATE_M_C_PARAM_W;
                            end
                          C_M_C_ADDR_GPIO1:
                            begin
                              class_obj_0000_gpio1_in <= sync_a2b_data;
                              class_obj_0000_gpio1_we <= 1'b1;
                              state_m_c <= C_STATE_M_C_PARAM_W;
                            end
                          C_M_C_ADDR_GPIO2:
                            begin
                              class_obj_0000_gpio2_in <= sync_a2b_data;
                              class_obj_0000_gpio2_we <= 1'b1;
                              state_m_c <= C_STATE_M_C_PARAM_W;
                            end
                          C_M_C_ADDR_GPIO3:
                            begin
                              class_obj_0000_gpio3_in <= sync_a2b_data;
                              class_obj_0000_gpio3_we <= 1'b1;
                              state_m_c <= C_STATE_M_C_PARAM_W;
                            end
                          default: ;
                        endcase
                      end
                    else
                      begin
                        // parameter read
                        case (sync_a2b_addr)
                          C_M_C_ADDR_GPIO0:
                            begin
                              sync_b_data <= class_obj_0000_gpio0_out;
                            end
                          C_M_C_ADDR_GPIO1:
                            begin
                              sync_b_data <= class_obj_0000_gpio1_out;
                            end
                          C_M_C_ADDR_GPIO2:
                            begin
                              sync_b_data <= class_obj_0000_gpio2_out;
                            end
                          C_M_C_ADDR_GPIO3:
                            begin
                              sync_b_data <= class_obj_0000_gpio3_out;
                            end
                          default: ;
                        endcase
                        state_m_c <= C_STATE_M_C_PARAM_R;
                      end
                  end
              end
            C_STATE_M_C_INIT1:
              begin
                class_obj_0000_init_req <= 1'b0; // pulse
                if (class_obj_0000_init_busy == 1'b1)
                  begin
                    state_m_c <= C_STATE_M_C_INIT2;
                  end
              end
            C_STATE_M_C_INIT2:
              begin
                if (class_obj_0000_init_busy == 1'b0)
                  begin
                    sync_b_ack <= 1'b1;
                    state_m_c <= C_STATE_M_C_FINISH;
                  end
              end
            C_STATE_M_C_RUN1:
              begin
                class_obj_0000_run_req <= 1'b0; // pulse
                if (class_obj_0000_run_busy == 1'b1)
                  begin
                    state_m_c <= C_STATE_M_C_RUN2;
                  end
              end
            C_STATE_M_C_RUN2:
              begin
                if (class_obj_0000_run_busy == 1'b0)
                  begin
                    sync_b_ack <= 1'b1;
                    state_m_c <= C_STATE_M_C_FINISH;
                  end
              end
            C_STATE_M_C_PARAM_W:
              begin
                case (m_c_address)
                  C_M_C_ADDR_GPIO0:
                    begin
                      class_obj_0000_gpio0_we <= 1'b0;
                    end
                  C_M_C_ADDR_GPIO1:
                    begin
                      class_obj_0000_gpio1_we <= 1'b0;
                    end
                  C_M_C_ADDR_GPIO2:
                    begin
                      class_obj_0000_gpio2_we <= 1'b0;
                    end
                  C_M_C_ADDR_GPIO3:
                    begin
                      class_obj_0000_gpio3_we <= 1'b0;
                    end
                  default: ;
                endcase
                sync_b_ack <= 1'b1;
                state_m_c <= C_STATE_M_C_FINISH;
              end
            C_STATE_M_C_PARAM_R:
              begin
                sync_b_ack <= 1'b1;
                state_m_c <= C_STATE_M_C_FINISH;
              end
            C_STATE_M_C_FINISH:
              begin
                if (sync_a2b_req == 1'b0)
                  begin
                    sync_b_ack <= 1'b0;
                    state_m_c <= C_STATE_M_C_IDLE;
                  end
              end
            default: ;
          endcase
        end
    end

  // AXI master write
  assign M_AXI_WLAST = (m_w_burst_count == m_axi_awlen) ? 1'b1 : 1'b0;
  always @(posedge M_AXI_ACLK)
    begin
      if (M_AXI_ARESETN == 1'b0)
        begin
          m_axi_awid <= 1'b0;
          m_axi_awburst <= 2'b01; // INCR
          m_axi_awlock <= 2'b00;
          m_axi_awcache <= 4'b0011; // Normal non-cacheable bufferable
          m_axi_awprot <= 3'b000;
          m_axi_awvalid <= 1'b0;
          m_axi_wstrb <= {C_M_STRB_WIDTH{1'b1}};
          m_axi_awaddr <= 32'h00000000;
          m_axi_wdata <= 32'h00000000;
          m_axi_awlen <= 1'd0;
          class_obj_0000_wbusy_in <= 1'b0;
          class_obj_0000_wbusy_we <= 1'b1;
          state_m_w <= C_STATE_M_W_IDLE;
        end
      else
        begin
          case (state_m_w)
            C_STATE_M_W_IDLE:
              begin
                if (class_obj_0000_wreq_out == 1'b1)
                  begin
                    class_obj_0000_wbusy_in <= 1'b1;
                    class_obj_0000_wbusy_we <= 1'b1;
                    m_axi_awaddr <= class_obj_0000_waddr_out;
                    m_axi_awlen <= class_obj_0000_wburst_out;
                    m_axi_wdata <= class_obj_0000_wdata_out;
                    m_axi_awvalid <= 1'b1;
                    state_m_w <= C_STATE_M_W_ADDR;
                  end
              end
            C_STATE_M_W_ADDR:
              begin
                if (M_AXI_AWREADY == 1'b1)
                  begin
                    m_axi_bready <= 1'b1;
                    m_axi_awvalid <= 1'b0;
                    m_axi_wvalid <= 1'b1;
                    m_w_burst_count <= 8'd0;
                    state_m_w <= C_STATE_M_W_DATA;
                  end
              end
            C_STATE_M_W_DATA:
              begin
                if (M_AXI_WREADY == 1'b1)
                  begin
                    if (m_w_burst_count < m_axi_awlen)
                      begin
                        m_w_burst_count <= m_w_burst_count + 1'd1;
                      end
                    else
                      begin
                        m_axi_wvalid <= 1'b0;
                        state_m_w <= C_STATE_M_W_RESPONSE;
                      end
                  end
              end
            C_STATE_M_W_RESPONSE:
              begin
                if (M_AXI_BVALID == 1'b1)
                  begin
                    m_axi_bready <= 1'b0;
                    class_obj_0000_wbusy_in <= 1'b0;
                    class_obj_0000_wbusy_we <= 1'b1;
                    state_m_w <= C_STATE_M_W_IDLE;
                  end
              end
            default: ;
          endcase
        end
    end

  // AXI master read
  always @(posedge M_AXI_ACLK)
    begin
      if (M_AXI_ARESETN == 1'b0)
        begin
          m_axi_arid <= 1'b0;
          m_axi_arburst <= 2'b01; // INCR
          m_axi_arlock <= 2'b00;
          m_axi_arcache <= 4'b0011; // Normal non-cacheable bufferable
          m_axi_arprot <= 3'b000;
          m_axi_arlen <= 8'd0;
          m_axi_araddr <= 32'h00000000;
          class_obj_0000_rbusy_in <= 1'b0;
          class_obj_0000_rbusy_we <= 1'b1;
          state_m_r <= C_STATE_M_R_IDLE;
        end
      else
        begin
          case (state_m_r)
            C_STATE_M_R_IDLE:
              begin
                if (class_obj_0000_rreq_out == 1'b1)
                  begin
                    class_obj_0000_rbusy_in <= 1'b1;
                    class_obj_0000_rbusy_we <= 1'b1;
                    m_axi_araddr <= class_obj_0000_raddr_out;
                    m_axi_arvalid <= 1'b1;
                    m_axi_rready <= 1'b0;
                    state_m_r <= C_STATE_M_R_ADDR;
                  end
              end
            C_STATE_M_R_ADDR:
              begin
                if (M_AXI_ARREADY == 1'b1)
                  begin
                    m_axi_arvalid <= 1'b0;
                    m_axi_rready <= 1'b1;
                    state_m_r <= C_STATE_M_R_DATA;
                  end
              end
            C_STATE_M_R_DATA:
              begin
                if (M_AXI_RVALID == 1'b1)
                  begin
                    class_obj_0000_rdata_in <= M_AXI_RDATA;
                    class_obj_0000_rdata_we <= 1'b1;
                    m_axi_rready <= 1'b0;
                    class_obj_0000_rbusy_in <= 1'b0;
                    class_obj_0000_rbusy_we <= 1'b1;
                    state_m_r <= C_STATE_M_R_IDLE;
                  end
              end
            default: ;
          endcase
        end
    end


  cdc_synchronizer
    #(
      .C_SYNC_ADDR_MSB ( C_SYNC_ADDR_MSB )
      )
  cdc_synchronizer_0
    (
     .A_CLK (S_AXI_ACLK),
     .A_RESETN (S_AXI_ARESETN),
     .A_REQ (sync_a_req),
     .A_ADDR (sync_a_addr),
     .A_DATA (sync_a_data),
     .A_WE (sync_a_we),
     .B_CLK (M_AXI_ACLK),
     .B_RESETN (M_AXI_ARESETN),
     .B_ACK (sync_b_ack),
     .B_DATA (sync_b_data),
     .A2B_REQ (sync_a2b_req),
     .A2B_ADDR (sync_a2b_addr),
     .A2B_DATA (sync_a2b_data),
     .A2B_WE (sync_a2b_we),
     .B2A_DATA (sync_b2a_data),
     .B2A_ACK (sync_b2a_ack)
     );

  // copied from Top.v (2)
  // -- begin --
  Template class_obj_0000
  (
    .clk(class_obj_0000_clk),
    .reset(class_obj_0000_reset),
    .waddr_in(class_obj_0000_waddr_in),
    .waddr_we(class_obj_0000_waddr_we),
    .waddr_out(class_obj_0000_waddr_out),
    .wdata_in(class_obj_0000_wdata_in),
    .wdata_we(class_obj_0000_wdata_we),
    .wdata_out(class_obj_0000_wdata_out),
    .wburst_in(class_obj_0000_wburst_in),
    .wburst_we(class_obj_0000_wburst_we),
    .wburst_out(class_obj_0000_wburst_out),
    .wreq_in(class_obj_0000_wreq_in),
    .wreq_we(class_obj_0000_wreq_we),
    .wreq_out(class_obj_0000_wreq_out),
    .wbusy_in(class_obj_0000_wbusy_in),
    .wbusy_we(class_obj_0000_wbusy_we),
    .wbusy_out(class_obj_0000_wbusy_out),
    .raddr_in(class_obj_0000_raddr_in),
    .raddr_we(class_obj_0000_raddr_we),
    .raddr_out(class_obj_0000_raddr_out),
    .rdata_in(class_obj_0000_rdata_in),
    .rdata_we(class_obj_0000_rdata_we),
    .rdata_out(class_obj_0000_rdata_out),
    .rreq_in(class_obj_0000_rreq_in),
    .rreq_we(class_obj_0000_rreq_we),
    .rreq_out(class_obj_0000_rreq_out),
    .rbusy_in(class_obj_0000_rbusy_in),
    .rbusy_we(class_obj_0000_rbusy_we),
    .rbusy_out(class_obj_0000_rbusy_out),
    .gpio0_in(class_obj_0000_gpio0_in),
    .gpio0_we(class_obj_0000_gpio0_we),
    .gpio0_out(class_obj_0000_gpio0_out),
    .gpio1_in(class_obj_0000_gpio1_in),
    .gpio1_we(class_obj_0000_gpio1_we),
    .gpio1_out(class_obj_0000_gpio1_out),
    .gpio2_in(class_obj_0000_gpio2_in),
    .gpio2_we(class_obj_0000_gpio2_we),
    .gpio2_out(class_obj_0000_gpio2_out),
    .gpio3_in(class_obj_0000_gpio3_in),
    .gpio3_we(class_obj_0000_gpio3_we),
    .gpio3_out(class_obj_0000_gpio3_out),
    .init_busy(class_obj_0000_init_busy),
    .init_req(class_obj_0000_init_req),
    .run_busy(class_obj_0000_run_busy),
    .run_req(class_obj_0000_run_req),
    .start_busy(class_obj_0000_start_busy),
    .start_req(class_obj_0000_start_req),
    .join_busy(class_obj_0000_join_busy),
    .join_req(class_obj_0000_join_req),
    .yield_busy(class_obj_0000_yield_busy),
    .yield_req(class_obj_0000_yield_req)
  );
  // -- end --

endmodule

// synchronizer for clock domain crossing
// need to implement these state machines
// ---- state machine A ----
// 1. to start, if B2A_ACK == low, goto 2 else goto 1
// 2. set A_ADDR and A_DATA, set A_REQ high
// 3. poll B2A_ACK, if B2A_ACK == high, set A_REQ low
// 4. poll B2A_ACK, if B2A_ACK == low, goto 1
// ---- state machine B ----
// 1. poll A2B_REQ, if A2B_REQ == high and B_ACK == low, get A_ADDR and A_DATA, set B_ACK high
// 2. poll A2B_REQ, if A2B_REQ == low, set B_ACK low, goto 1
module cdc_synchronizer
  #(
    parameter C_SYNC_ADDR_MSB = 7
    )
  (
   input                          A_CLK,
   input                          A_RESETN,
   input                          A_REQ,
   input [C_SYNC_ADDR_MSB:0]      A_ADDR,
   input [31:0]                   A_DATA,
   input                          A_WE,
   input                          B_CLK,
   input                          B_RESETN,
   input                          B_ACK,
   input [31:0]                   B_DATA,
   output reg                     A2B_REQ,
   output reg [C_SYNC_ADDR_MSB:0] A2B_ADDR,
   output reg [31:0]              A2B_DATA,
   output reg                     A2B_WE,
   output reg [31:0]              B2A_DATA,
   output reg                     B2A_ACK
   );

  reg                             req [2:0];
  reg                             ack [2:0];
  reg                             we [1:0];
  reg [C_SYNC_ADDR_MSB:0]         addr[1:0];
  reg [31:0]                      data_a[1:0];
  reg [31:0]                      data_b[1:0];
  reg [3:0]                       ia;
  reg [3:0]                       ib;

  always @(posedge A_CLK)
    begin
      if (A_RESETN == 1'b0)
        begin
          B2A_ACK <= 1'b0;
          B2A_DATA <= 1'b0;
          for (ia = 4'd0; ia < 4'd3; ia = ia + 4'd1)
            begin
              ack[ia] <= 1'b0;
            end
        end
      else
        begin
          ack[0] <= B_ACK;
          ack[1] <= ack[0];
          ack[2] <= ack[1];
          data_b[0] <= B_DATA;
          data_b[1] <= data_b[0];
          B2A_DATA <= data_b[1];
          if ((ack[1] == ack[2]) && (ack[2] != B2A_ACK))
            B2A_ACK <= ack[2];
        end
    end

  always @(posedge B_CLK)
    begin
      if (B_RESETN == 1'b0)
        begin
          A2B_REQ <= 1'b0;
          A2B_ADDR <= 1'b0;
          A2B_DATA <= 1'b0;
          A2B_WE <= 1'b0;
          for (ib = 4'd0; ib < 4'd3; ib = ib + 4'd1)
            begin
              req[ib] <= 1'b0;
            end
        end
      else
        begin
          req[0] <= A_REQ;
          req[1] <= req[0];
          req[2] <= req[1];
          we[0] <= A_WE;
          we[1] <= we[0];
          A2B_WE <= we[1];
          addr[0] <= A_ADDR;
          addr[1] <= addr[0];
          A2B_ADDR <= addr[1];
          data_a[0] <= A_DATA;
          data_a[1] <= data_a[0];
          A2B_DATA <= data_a[1];
          if ((req[1] == req[2]) && (req[2] != A2B_REQ))
            A2B_REQ <= req[2];
        end
    end
endmodule

synthesijer_template_tb.v : synthesijer_template.vのテストベンチ
/*
  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.
*/


`timescale 1ns / 1ps

module synthesijer_template_tb;

  parameter C_S_AXI_DATA_WIDTH = 32;
  parameter C_BASEADDR = 32'h60000000;
  parameter C_HIGHADDR = 32'h600000FF;
  parameter C_M_AXI_DATA_WIDTH = 32;
  parameter C_M_STRB_WIDTH = 4;
  parameter C_M_AXI_THREAD_ID_WIDTH = 1;

  parameter STEP = 10; // 100MHz
  parameter STEPM = 6; // 166MHz

  parameter BASE_ADDR = 32'h60000000;
  parameter REG_ADDR_CONTROL = 32'h0;
  parameter REG_ADDR_WDATA = 32'h4;
  parameter REG_ADDR_ACK = 32'h8;
  parameter REG_ADDR_RDATA = 32'hC;

  reg S_AXI_ACLK;
  reg S_AXI_ARESETN;
  reg [31:0] S_AXI_AWADDR;
  reg [2:0]  S_AXI_AWPROT;
  reg        S_AXI_AWVALID;
  wire       S_AXI_AWREADY;
  reg [31:0] S_AXI_WDATA;
  reg [3:0]  S_AXI_WSTRB;
  reg        S_AXI_WVALID;
  wire       S_AXI_WREADY;
  wire [1:0] S_AXI_BRESP;
  wire       S_AXI_BVALID;
  reg        S_AXI_BREADY;
  reg [31:0] S_AXI_ARADDR;
  reg [2:0]  S_AXI_ARPROT;
  reg        S_AXI_ARVALID;
  wire       S_AXI_ARREADY;
  wire [31:0] S_AXI_RDATA;
  wire [1:0]  S_AXI_RRESP;
  wire        S_AXI_RVALID;
  reg         S_AXI_RREADY;
  reg         M_AXI_ACLK;
  reg         M_AXI_ARESETN;
  wire [C_M_AXI_THREAD_ID_WIDTH - 1:0] M_AXI_AWID;
  wire [31:0]                          M_AXI_AWADDR;
  wire [7:0]                           M_AXI_AWLEN;
  wire [2:0]                           M_AXI_AWSIZE;
  wire [1:0]                           M_AXI_AWBURST;
  wire [1:0]                           M_AXI_AWLOCK;
  wire [3:0]                           M_AXI_AWCACHE;
  wire [2:0]                           M_AXI_AWPROT;
  wire                                 M_AXI_AWVALID;
  reg                                  M_AXI_AWREADY;
  wire [C_M_AXI_DATA_WIDTH - 1:0]      M_AXI_WDATA;
  wire [C_M_STRB_WIDTH - 1:0]          M_AXI_WSTRB;
  wire                                 M_AXI_WLAST;
  wire                                 M_AXI_WVALID;
  reg                                  M_AXI_WREADY;
  reg [C_M_AXI_THREAD_ID_WIDTH - 1:0]  M_AXI_BID;
  reg [1:0]                            M_AXI_BRESP;
  reg                                  M_AXI_BVALID;
  wire                                 M_AXI_BREADY;
  wire [C_M_AXI_THREAD_ID_WIDTH - 1:0] M_AXI_ARID;
  wire [31:0]                          M_AXI_ARADDR;
  wire [7:0]                           M_AXI_ARLEN;
  wire [2:0]                           M_AXI_ARSIZE;
  wire [1:0]                           M_AXI_ARBURST;
  wire [1:0]                           M_AXI_ARLOCK;
  wire [3:0]                           M_AXI_ARCACHE;
  wire [2:0]                           M_AXI_ARPROT;
  wire                                 M_AXI_ARVALID;
  reg                                  M_AXI_ARREADY;
  reg [C_M_AXI_THREAD_ID_WIDTH - 1:0]  M_AXI_RID;
  reg [C_M_AXI_DATA_WIDTH - 1:0]       M_AXI_RDATA;
  reg [1:0]                            M_AXI_RRESP;
  reg                                  M_AXI_RLAST;
  reg                                  M_AXI_RVALID;
  wire                                 M_AXI_RREADY;

  reg [31:0]                           master_read_data;
  reg [31:0]                           app_read_data;
  integer                              i;

  initial
    begin
      $dumpfile("wave.vcd");
      $dumpvars(5, synthesijer_template_tb);
    end

  // generate clock slave
  initial
    begin
      S_AXI_ACLK = 1'b1;
      forever
        begin
          #(STEP / 2) S_AXI_ACLK = ~S_AXI_ACLK;
        end
    end

  // generate reset slave
  initial
    begin
      S_AXI_ARESETN <= 1'b1;
      @(posedge S_AXI_ACLK) S_AXI_ARESETN <= 1'b0;
      @(posedge S_AXI_ACLK) S_AXI_ARESETN <= 1'b1;
    end

  // generate clock master
  initial
    begin
      M_AXI_ACLK = 1'b1;
      forever
        begin
          #(STEPM / 2) M_AXI_ACLK = ~M_AXI_ACLK;
          if ($stime > 10000)
            begin
              $finish;
            end
        end
    end

  // generate reset master
  initial
    begin
      M_AXI_ARESETN <= 1'b1;
      @(posedge M_AXI_ACLK) M_AXI_ARESETN <= 1'b0;
      @(posedge M_AXI_ACLK) M_AXI_ARESETN <= 1'b1;
    end

  // fake axi master write
  task fake_axi_master_write
    (
     input [31:0] addr,
     input [31:0] data
     );
    begin
      S_AXI_AWADDR <= addr;
      S_AXI_AWVALID <= 1'b1;
      @(posedge S_AXI_ACLK);
      S_AXI_AWVALID <= 1'b0;
      S_AXI_WDATA <= data;
      S_AXI_WVALID <= 1'b1;
      @(posedge S_AXI_ACLK);
      S_AXI_WVALID <= 1'b0;
      S_AXI_BREADY <= 1'b1;
      @(posedge S_AXI_ACLK);
      S_AXI_BREADY <= 1'b0;
      @(posedge S_AXI_ACLK);
    end
  endtask

  // fake axi master read
  task fake_axi_master_read
    (
     input [31:0] addr
     );
    begin
      S_AXI_ARADDR <= addr;
      S_AXI_ARVALID <= 1'b1;
      repeat(2) @(posedge S_AXI_ACLK);
      S_AXI_ARVALID <= 1'b0;
      master_read_data <= S_AXI_RDATA;
      S_AXI_RREADY <= 1'b1;
      @(posedge S_AXI_ACLK);
      S_AXI_RREADY <= 1'b0;
      @(posedge S_AXI_ACLK);
    end
  endtask

  // Application interface: Data write
  task app_write
    (
     input [7:0] reg_addr,
     input [31:0] data
     );
    begin
      // poll ack == 0
      fake_axi_master_read(BASE_ADDR + REG_ADDR_ACK);
      while (master_read_data[0] != 1'b0)
        begin
          fake_axi_master_read(BASE_ADDR + REG_ADDR_ACK);
        end
      // write data
      fake_axi_master_write(BASE_ADDR + REG_ADDR_WDATA, data);
      // write control
      fake_axi_master_write(BASE_ADDR + REG_ADDR_CONTROL, {reg_addr, 22'b0, 1'b1, 1'b1});
      // poll ack == 1
      fake_axi_master_read(BASE_ADDR + REG_ADDR_ACK);
      while (master_read_data[0] != 1'b1)
        begin
          fake_axi_master_read(BASE_ADDR + REG_ADDR_ACK);
        end
      // write req <= 0
      fake_axi_master_write(BASE_ADDR + REG_ADDR_CONTROL, {reg_addr, 22'b0, 1'b1, 1'b0});
      // poll ack == 0
      fake_axi_master_read(BASE_ADDR + REG_ADDR_ACK);
      while (master_read_data[0] != 1'b0)
        begin
          fake_axi_master_read(BASE_ADDR + REG_ADDR_ACK);
        end
    end
  endtask

  // Application interface: Data read
  task app_read
    (
     input [7:0] reg_addr
     );
    begin
      // poll ack == 0
      fake_axi_master_read(BASE_ADDR + REG_ADDR_ACK);
      while (master_read_data[0] != 1'b0)
        begin
          fake_axi_master_read(BASE_ADDR + REG_ADDR_ACK);
        end
      // write control
      fake_axi_master_write(BASE_ADDR + REG_ADDR_CONTROL, {reg_addr, 22'b0, 1'b0, 1'b1});
      // poll ack == 1
      fake_axi_master_read(BASE_ADDR + REG_ADDR_ACK);
      while (master_read_data[0] != 1'b1)
        begin
          fake_axi_master_read(BASE_ADDR + REG_ADDR_ACK);
        end
      // write req <= 0
      fake_axi_master_write(BASE_ADDR + REG_ADDR_CONTROL, {reg_addr, 22'b0, 1'b0, 1'b0});
      // poll ack == 0
      fake_axi_master_read(BASE_ADDR + REG_ADDR_ACK);
      while (master_read_data[0] != 1'b0)
        begin
          fake_axi_master_read(BASE_ADDR + REG_ADDR_ACK);
        end
      // read data
      fake_axi_master_read(BASE_ADDR + REG_ADDR_RDATA);
      app_read_data = master_read_data;
    end
  endtask

  // initialize
  initial
    begin
      repeat(4) @(posedge S_AXI_ACLK);

      // run Test
      app_write(8'd1, 32'h2d100000); // fb address
      app_write(8'd2, 32'd5120); // line bytes
      app_write(8'd3, 32'd11); // dot size
      app_write(8'd0, 32'd0); // run init()
      app_write(8'd0, 32'd1); // run run()

      repeat(4) @(posedge S_AXI_ACLK);
      $finish;
    end

  // fake master->slave
  always @(posedge M_AXI_ACLK)
    begin
      if (M_AXI_AWVALID == 1'b1)
        begin
          M_AXI_AWREADY <= 1'b1;
        end
      if (M_AXI_WVALID == 1'b1)
        begin
          M_AXI_AWREADY <= 1'b0;
          M_AXI_WREADY <= 1'b1;
        end
      if (M_AXI_WVALID == 1'b0)
        begin
          M_AXI_WREADY <= 1'b0;
        end
      if ((M_AXI_WLAST == 1'b1) && (M_AXI_WREADY == 1'b1))
        begin
          M_AXI_BVALID <= 1'b1;
        end
      if (M_AXI_BREADY == 1'b0)
        begin
          M_AXI_BVALID <= 1'b0;
        end
    end

  // fake slave->master
  integer m2s_burst_len;
  integer m2s_burst_count;
  integer m2s_data;
  integer m2s_i;
  initial
    begin
      M_AXI_RLAST <= 1'b0;
    end
  always @(posedge M_AXI_ACLK)
    begin
      if (M_AXI_ARVALID == 1'b1)
        begin
          M_AXI_ARREADY <= 1'b1;
          m2s_burst_len <= M_AXI_ARLEN;
          m2s_burst_count <= 0;
          @(posedge M_AXI_ACLK);
          M_AXI_ARREADY <= 1'b0;
          for (m2s_i = 0; m2s_i <= m2s_burst_len; m2s_i = m2s_i + 1)
            begin
              M_AXI_RDATA <= m2s_i;
              M_AXI_RVALID <= 1'b1;
              if (m2s_i == m2s_burst_len)
                begin
                  M_AXI_RLAST <= 1'b1;
                end
              @(posedge M_AXI_ACLK);
            end
          M_AXI_RVALID <= 1'b0;
          M_AXI_RLAST <= 1'b0;
          @(posedge M_AXI_ACLK);
        end
    end

  synthesijer_template
    #(
      .C_S_AXI_DATA_WIDTH ( C_S_AXI_DATA_WIDTH ),
      .C_BASEADDR ( C_BASEADDR ),
      .C_HIGHADDR ( C_HIGHADDR ),
      .C_M_AXI_DATA_WIDTH ( C_M_AXI_DATA_WIDTH ),
      .C_M_STRB_WIDTH ( C_M_STRB_WIDTH ),
      .C_M_AXI_THREAD_ID_WIDTH ( C_M_AXI_THREAD_ID_WIDTH )
      )
  synthesijer_template_0
    (
     .S_AXI_ACLK ( S_AXI_ACLK ),
     .S_AXI_ARESETN ( S_AXI_ARESETN ),
     .S_AXI_AWADDR ( S_AXI_AWADDR ),
     .S_AXI_AWPROT ( S_AXI_AWPROT ),
     .S_AXI_AWVALID ( S_AXI_AWVALID ),
     .S_AXI_AWREADY ( S_AXI_AWREADY ),
     .S_AXI_WDATA ( S_AXI_WDATA ),
     .S_AXI_WSTRB ( S_AXI_WSTRB ),
     .S_AXI_WVALID ( S_AXI_WVALID ),
     .S_AXI_WREADY ( S_AXI_WREADY ),
     .S_AXI_BRESP ( S_AXI_BRESP ),
     .S_AXI_BVALID ( S_AXI_BVALID ),
     .S_AXI_BREADY ( S_AXI_BREADY ),
     .S_AXI_ARADDR ( S_AXI_ARADDR ),
     .S_AXI_ARPROT ( S_AXI_ARPROT ),
     .S_AXI_ARVALID ( S_AXI_ARVALID ),
     .S_AXI_ARREADY ( S_AXI_ARREADY ),
     .S_AXI_RDATA ( S_AXI_RDATA ),
     .S_AXI_RRESP ( S_AXI_RRESP ),
     .S_AXI_RVALID ( S_AXI_RVALID ),
     .S_AXI_RREADY ( S_AXI_RREADY ),
     .M_AXI_ACLK ( M_AXI_ACLK ),
     .M_AXI_ARESETN ( M_AXI_ARESETN ),
     .M_AXI_AWID ( M_AXI_AWID ),
     .M_AXI_AWADDR ( M_AXI_AWADDR ),
     .M_AXI_AWLEN ( M_AXI_AWLEN ),
     .M_AXI_AWSIZE ( M_AXI_AWSIZE ),
     .M_AXI_AWBURST ( M_AXI_AWBURST ),
     .M_AXI_AWLOCK ( M_AXI_AWLOCK ),
     .M_AXI_AWCACHE ( M_AXI_AWCACHE ),
     .M_AXI_AWPROT ( M_AXI_AWPROT ),
     .M_AXI_AWVALID ( M_AXI_AWVALID ),
     .M_AXI_AWREADY ( M_AXI_AWREADY ),
     .M_AXI_WDATA ( M_AXI_WDATA ),
     .M_AXI_WSTRB ( M_AXI_WSTRB ),
     .M_AXI_WLAST ( M_AXI_WLAST ),
     .M_AXI_WVALID ( M_AXI_WVALID ),
     .M_AXI_WREADY ( M_AXI_WREADY ),
     .M_AXI_BID ( M_AXI_BID ),
     .M_AXI_BRESP ( M_AXI_BRESP ),
     .M_AXI_BVALID ( M_AXI_BVALID ),
     .M_AXI_BREADY ( M_AXI_BREADY ),
     .M_AXI_ARID ( M_AXI_ARID ),
     .M_AXI_ARADDR ( M_AXI_ARADDR ),
     .M_AXI_ARLEN ( M_AXI_ARLEN ),
     .M_AXI_ARSIZE ( M_AXI_ARSIZE ),
     .M_AXI_ARBURST ( M_AXI_ARBURST ),
     .M_AXI_ARLOCK ( M_AXI_ARLOCK ),
     .M_AXI_ARCACHE ( M_AXI_ARCACHE ),
     .M_AXI_ARPROT ( M_AXI_ARPROT ),
     .M_AXI_ARVALID ( M_AXI_ARVALID ),
     .M_AXI_ARREADY ( M_AXI_ARREADY ),
     .M_AXI_RID ( M_AXI_RID ),
     .M_AXI_RDATA ( M_AXI_RDATA ),
     .M_AXI_RRESP ( M_AXI_RRESP ),
     .M_AXI_RLAST ( M_AXI_RLAST ),
     .M_AXI_RVALID ( M_AXI_RVALID ),
     .M_AXI_RREADY ( M_AXI_RREADY )
     );
endmodule

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 <linux/fb.h>
#include <stdint.h>

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

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 dotsize = height / VIRTUAL_SCREEN_SIZE;
  uint32_t windowsize = VIRTUAL_SCREEN_SIZE * dotsize;
  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);

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

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

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

参考文献

ライフゲーム(Wikipedia)

Synthesijerサンプルプログラム