NSLで回路を作ろう#07: カメラキャプチャ画像をディスプレイに表示 (2)

前回の続き 
- 1 回路について、SCCBコントローラ回路
- 2 カメラキャプチャ回路(今回)
- 3 ソフトウェアプログラム&動作確認

前回はシステムの概要とSCCBコントローラ回路の作成について見てきた。
- 今回は、その次のステップとして、カメラキャプチャ回路の作成を行う

# カメラキャプチャ回路

回路のイメージ図:
![cap_ip-img_v00.dio.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3588818/c1777148-1b8d-2342-d3bd-03caa3cbdc0f.png)

CMOSカメラから送られてきた画像データをAvalon-MMを使用してSDRAMに転送する。
- データ転送にはAvalon-MMのバースト転送を用いる

# カメラ入力受取り

カメラ入力受取りの動作をタイミング図にするとこんな感じ
 ![cam_in_00.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3588818/6d0a310a-4746-2dd5-0b31-1896769015b2.png)

- カメラから画像データが入力されるタイミング
  - 水平出力同期信号のbitが1の間、カメラからの画像データを取込み続ける
- 画像データは2つのデータを合わせて1つの画素値
  - FIFOに書込むのは2データ取込んだタイミングで書込みを1回行う
- 1ラインはPCLKの1568サイクル分
  - 最初の1280サイクルはデータ入力が有効
  - 後ろの288サイクルではデータ入力が無効



# バースト転送の動作

バースト転送の動作をタイミング図にするとこんな感じ
![burst_00.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3588818/fcc43826-7b7d-26a7-be39-afa934a630ff.png)

- FIFOにデータが入っていることを確認してから、データ転送を開始
  - Avalon-MMのバースト転送機能を用い、1回の転送では連続した16データを転送する
- 連続したデータの内の１データ転送ができたかどうかはwaitrequest信号で判断する
  - waitrequest信号の入力が1の場合
    - 入力が0になるまで、同じデータの出力を保持し続ける
  - waitrequest信号の入力が0の場合
    - データ転送が1データ分できたと判断
      - FIFOから次のデータを取り出して送信
- データ転送回数をカウンタで数え上げる
  - カウンタが最大値(15)のときに転送が完了した場合
    - １回分のバースト転送を終了し、次のデータが集まるまで待機する


# カメラ入力フォーマット

CMOSカメラモジュールOV7670は画像データの出力形式を設定できる
- 今回はRGB444形式を用いる
  
データフォーマットは以下の通り

![cam_in_rgb444.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3588818/eeea5479-5d01-0cbb-24dd-3248c1aa307e.png)

# モジュール設定

モジュール設定にPIOを経由して、CPUからの設定を行えるようにする。[^1]

[^1]: PIO_[345]_BASEはプログラム上で使う名前。各アドレスの実値はPlatformDesignerで作成するときに割り当てられる。

PIOで扱うbit：
![cap_ip_pio.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3588818/b2441ca3-3bd3-0195-950b-e47aa55e5adb.png)

それぞれのbitの扱い
- CAPADDAR: 画像データの転送先アドレス
  - SDRAMのどこに転送するのかをここへ設定する
  - この値を先頭アドレスとして順番に画像データを転送していく
- CAPON: カメラキャプチャ有効bit
  - この値が1の場合、本モジュールがカメラから入力されてくるデータを取込むようになる
- CBLANK: カメラのデータ転送の空白期間であることを示すフラグbit
  - カメラからの垂直同期信号を取込んで空白期間を判断し、CPUに伝える
  - CPUはこのbitを読み、空白期間を判断
    - 空白期間であるときCPUはCAPADDAR、CAPONのbitを書き換える
- CLRCBLNK: ブランクフラグクリア
  - このbitを１にすると、CBLANKの値を0にクリアする

カメラキャプチャ回路ではそれぞれのbitを以下の信号名として受け取る
- CAPADDR: coe_cm_CAPADDR[25:0]
- CAPON: coe_cm_CAPON
- CBLANK: coe_cm_CBLANK
- CLRCBLNK: coe_cm_CLRCBLNK


# 機能概要

まずは以下のブロック図の構成を考え、それぞれの中身を作成していく

![cap_ip_func_10.drawio.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3588818/69db22fc-ac93-7fbe-f8e8-d9c3dd3dda35.png)

CMOSカメラから入力されるデータ
- 一旦FIFOに格納
- データが十分に溜まるとSDRAMにデータを転送する
  - Avalon-MMのマスター回路実装してバースト転送時を行う。

転送先のアドレスや、キャプチャのオンオフ等の設定
- CPUのプログラムがPIO経由で設定する
- 設定を行うタイミング
  - ブランクフラグを参照する

ブランクフラグ
- １画面の開始を垂直同期信号が立ち上がりで検出
- 信号が立ち上がったタイミングで以下を行う
  - ブランクフラグの値を1に設定
  - FIFOのデータクリア
  - SDRAM転送管理内のステートマシンを初期状態に遷移

基本的にはシステムクロック(m_clock:100MHz)で駆動
- 一部のモジュールはCMOSカメラから供給されるクロック（PCLK:25MHz）で駆動
- 図中の点線を境に左側がPCLK、右側がm_clockで駆動するモジュール
  - FIFOは書込み、読出しで別々のクロックで駆動する


# 機能一覧
ブロック図内の番号で示された箇所は、それぞれ以下の役割を担う。

[1] カメラ入力取り込み
- CMOSカメラから入力されてくるクロックや同期信号のタイミングで、
画像データを取込みFIFOに格納する。

[2] SDRAMデータ転送管理
- Avalon-MMマスターのバースト転送機能を用い、FIFOに格納されている
画像データをSDRAMに転送する。

[3] 空白期間判定
- 垂直同期信号を受取り、ソフトウェアプログラム側に、
データの書き込みが行われていない期間を伝える信号(CBLANK)を作成する

[4] 設定レジスタ
- PIOからの設定値をレジスタに格納し、各モジュールに伝える

[5] カメラ出力用XCLK生成
- CMOSカメラ(OV7670)に供給するクロック信号を分周カウンタによって生成する

[6] 一時保存用FIFO
- クロック系統の違う区間:PCLK(25MHz)⇔m_clock(100MHz)
で画像データの一時保存として格納するFIFO。

# サブモジュールに分割

機能一覧で説明したそれぞれの機能の実装にはサブモジュールに分割して行う。
- 各機能に対し1つのサブモジュールを割り当てる

機能とモジュール対応表:
![submodules.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3588818/97a96c0d-ed28-9330-4586-e551bf6d4297.png)

以降でそれぞれのサブモジュールについて説明する。

# input_ov7670モジュール

概要
- CMOSカメラから画像データ(CAMDATA)を2段のFFで取込み
- 24bitの画素値に変換してFIFOに格納
- クロック系統が違う区間をまたぐ以下の信号は一旦FFで受けて同期化する
  - blank_flagへ出力する垂直同期信号
  - conf_regから読込むキャプチャオン信号
- 画像データを格納するタイミング
  - カメラから入力されてくるクロック(PCLK)や水平出力有効信号(HREF)を参照する
  - FIFOに格納する際は、入力されてきたデータを２回取込んだ後、１つのデータにまとめられて格納される

input_ov7670のブロック図
![input_ov7670_func_05.drawio.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3588818/41e939ac-f091-8380-c004-199601267268.png)

赤色の実線で囲われている部分がinput_ov7670の部分

## 機能一覧

先ほどのブロック図の中で分けられた部分は、それぞれ以下に示す役割を担う。

- (1) データ格納FF1
  - 水平出力有効信号が入力されているとき、カメラからの画像データを受取る
- (2) データ格納FF2
  - 水平出力有効信号が入力されているとき、(1)データ格納FF1に格納されている画像データを保管する
- (3) 画素値生成
  - (1),(2)のデータ格納FFの値を用いて、FIFOに書込む画素値を設定する
- (4) キャプチャオン取り込み
  - 設定レジスタから入力されてくるキャプチャオン信号が別クロック系統なので、一旦FFで受取る
- (5) 書込み有効チェック
  - キャプチャオン状態なのか水平出力有効信号が入力されているのかを判断
  - FIFOに書込むのかどうかを決定する
- (6) FIFO書込み信号生成
  - (5)が書込み有効であると判断したときに、FIFOに書込みを要求する出力を作成する
  - カメラのデータは2データで1画素分なので、2サイクルごとに信号を出力する
- (7) 垂直同期信号取込み
  - m_clock駆動の機能[3]空白期間判定で扱う垂直同期信号をPCLKで駆動している箇所で一旦FFを受けるため、実装している


## 実装ファイル

<details>
<summary>長いので折り畳み</summary>

ヘッダファイル
```nsl:input_ov7670.nsh
#ifndef _INPUT_OV7670_NSH
#define _INPUT_OV7670_NSH

declare input_ov7670 {
    input VSYNC;
    input HREF;
    input CAMDATA[8];

    output VSYNC_r;

    input CAPON;

    output FIFO_WR;
    output FIFO_IN[24];
}

#endif // _INPUT_OV7670_NSH

```

モジュール
```nsl:input_ov7670.nsl
#include "input_ov7670.nsh"

module input_ov7670 {
	// データ格納FF1
    reg dat1[8] = 0;
	//  データ格納FF2
    reg dat2[8] = 0;
	// CAPON信号の同期化
    reg cap_on_ff[2] = 0;
    // キャプチャオン有効
    wire is_cap_on;
    // HREFのタイミング調整用レジスタ
    reg HREF_r = 0;
    // FIFO書込み有効
    func_self fifo_wr_en();
    // FIFO書込み信号
    reg fifo_wr_ff = 0;
    // 垂直同期信号取込み
    reg vsync_ff = 0;
    
    // (1) データ格納FF1, (2) データ格納FF2
    if(HREF){
        dat2 := dat1;
        dat1 := CAMDATA;
    }

    // (3) 画素値生成
    FIFO_IN = {
        dat1[3:0], 4'h0, 
        dat1[7:4], 4'h0, 
        dat2[3:0], 4'h0
    };

    // (4) キャプチャオン取り込み
    cap_on_ff := {cap_on_ff[0], CAPON};

    // (5) 書込み有効チェック
    is_cap_on = cap_on_ff[1];
    HREF_r := HREF;
    if(HREF_r & is_cap_on) fifo_wr_en();

    // (6) FIFO書込み信号生成
    func fifo_wr_en {
        fifo_wr_ff:= ~fifo_wr_ff;
    }
    FIFO_WR = fifo_wr_ff;

    // (7) 垂直同期信号取込み
    vsync_ff := VSYNC;
    VSYNC_r = vsync_ff;
}
```

</details>


# sdram_transferモジュール

概要
- バースト転送を用いFIFOに格納されているデータをAvalon-MMインターフェイスを通じてSDRAMへバースト転送を行う。
- 転送の管理はステートマシンを実装し、回路を駆動させる。
  - ステートマシンの仕様については後述

sdram_transferのブロック図
![sdram_transfer_func_04_05.drawio.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3588818/1d10d6dd-8410-f6f0-d22b-a1d3de29d9a7.png)

赤色の実線で囲われている部分がsdram_transferの部分で、緑色線はステートマシンの状態を示す信号。
- ステートマシンは他の信号に比べ影響度が大きいので色分けした

## 機能一覧

先ほどのブロック図の中で分けられた部分は、それぞれ以下に示す役割を担う。

- (1) ステートマシン
  - sdram_transferモジュール全体の動作制御を行う
- (2) FIFO読込み信号生成	
  - FIFOへデータ読込要求信号を出力する。要求タイミングは以下の2つである
    - ① バースト転送開始前に先読み
    - ② バースト転送中に次のデータを読込み
- (3) SDRAM転送データ
  - FIFOから読込んだデータを出力できる形式に変換する
    - 24bit→32bitにbit幅拡張
- (4) 書込み要求信号出力
  - バースト転送中はAvalon-MMインターフェイスに書込み要求信号を出力する
- (5) バーストカウンタ
  - バースト転送が完了したかを判断するカウンタ
  - バースト転送時にwaitrequestが来ていないタイミングを数える
  - 値がバースト長-1になった場合終了信号を出力する
- (6) アドレスカウンタ
  - バースト転送毎にSDRAMの転送先アドレスを調整するためのカウンタ
  - 加算はFIFO待ちから解除されたときに行う
  - 加算する量はバースト長16×4Byte=64とする
- (7) アドレス設定
  - レジスタで指定されているアドレスとアドレスカウンタの値を合算して出力する
- (8) 読込み可能チェック[^4]
  - FIFOのデータ数を確認し、バースト転送できるか確認する
    - 有効: 「データ数がバースト長以上」かつ「キャプチャオンの状態である」の場合
    - 無効: そうでない場合
  
[^4]: テキストでは上位モジュールにあった論理。上位モジュールには何も論理を実装せず、配線のみにした方が良いという考えからこのような仕様にした。　

## ステートマシンについて
状態一覧表:
![cap_ip-state_summary.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3588818/f4cf0e7f-17ab-0ef0-9962-16c68f5ba05e.png)

各状態の説明及び動作の流れ
- HALT
  - 初期状態
  - 転送可能になり次第START状態へ
- START
  - バースト転送開始
  - 最初に転送するデータをFIFOから先読み
  - クロックを受け次第無条件でTRANSFERに遷移
- TRANSFER
  - バースト転送状態
  - 書込み信号を1にする
  - 転送時waitrequestに1が入力されていないことを確認して、転送したデータ数を数えていく
  - カウンタ値最大で最後のデータの送信完了と判断
　　- バースト転送終了。
  - 転送終了後、以下の条件に従って遷移
    - アドレスカウンタが最大値(1画面終了): HALT
    - それ以外: FIFOWAIT
- FIFOWAIT 
  - 次のデータがFIFOに集まるまで待機する
  - データ集まり次第、バースト転送を再開するため、STARTへ遷移
  - STARTへ遷移するとき、転送先アドレスを次の場所に移すため、アドレスカウンタの値を64加算する
    - 1回のバースト転送で4Byteのデータを16個送る→4×16=64Byte送る計算
- 例外: HALTに強制的に遷移
  - waitrequest信号の最長サイクルは定義されていない
  - バースト転送が終わる前に次の画面開始を示す垂直同期信号が来ること可能性を考慮する必要がある
    - 垂直同期信号が来ると、blank_flagからSET_HALT信号が入力される
  - SET_HALTが入力されたタイミングでは、転送を取りやめ、HALTへ強制的に遷移


ステートマシン図:
![cap_ip-state.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3588818/87cd31f1-84c7-fce9-3140-0f904628da2d.png)
- 前述したようにSET_HALT信号が１の場合は強制的にHALTに遷移する。

## 実装ファイル

<details>
<summary>長いので折り畳み</summary>

ヘッダファイル
```nsl:sdram_transfer.nsh
#ifndef _SDRAM_TRANSFER_NSH
#define _SDRAM_TRANSFER_NSH

#define BURST_LENGTH (16)
#define BURST_BYTES (16*4)
#define VGA_MAX (640*480*4)


declare sdram_transfer {
	func_out FIFO_RD();
    input DATA_CNT[10];
    input FIFO_OUT[24];

    func_in SET_HALT();
    
    input CAPON;
    input CAPADDR[26];
    
    input waitrequest;
    func_out write();
    output burstcount[5];
    output address[26];
    output writedata[32];
}

#endif // _SDRAM_TRANSFER_NSH
```

モジュール
```nsl:sdram_transfer.nsl
#include "sdram_transfer.nsh"

module sdram_transfer {
	// ステートレジスタ
    state_name HALT, START, TRANSFER, FIFOWAIT;
    // 転送回数カウント
    reg burst_cnt[4] = 0;
    // バースト転送終了
    func_self burst_end();
    // アドレスカウンタ
    reg addr_cnt[26] = 0;
    // 1画面終了判定
    func_self cap_end();
    // FIFO読込み可能チェック
    func_self can_fifo_read();

    state HALT {
        func can_fifo_read{
            goto START;
        }   

        burst_cnt := 0;
        addr_cnt := 0;
    }

    state START{
        func SET_HALT{
            goto HALT;
        }
        FIFO_RD();
        burst_cnt := 0;
        goto TRANSFER;
    }
    
    state TRANSFER{
        func SET_HALT{
            goto HALT;
        }
        func burst_end{
            if(cap_end){
                goto HALT;
            }else{
                goto FIFOWAIT;
            }
        }
        write();
        if(~waitrequest){
            if(burst_end){
                burst_cnt := 0;
            } else {
                burst_cnt++;
                FIFO_RD();
            }
        } 
    }

    state FIFOWAIT {
        func SET_HALT{
            goto HALT;
        }
        func can_fifo_read{
            addr_cnt := addr_cnt + BURST_BYTES;
            goto START;
        }
    }
    
    if (burst_cnt == BURST_LENGTH-1 && ~waitrequest) burst_end();
    if (addr_cnt == (VGA_MAX - BURST_BYTES )) cap_end();

    alt {
        CAPON == 0: {}
        DATA_CNT >= BURST_LENGTH: {can_fifo_read();}
        else: {}
    }

    address = addr_cnt+CAPADDR;
    writedata = {8'b0, FIFO_OUT};
    burstcount = 5'd16;
}
```

</details>


# blank_flagモジュール

概要
- input_ov7670モジュールが受け取った垂直同期信号をm_clockで同期化して受取る
  - 受け取った信号の値や、立上りエッジ検出したタイミングを他のモジュールに出力する
- 出力を受け取った各モジュールは以下のように動作する
  - sdram_transfer:
    - ステートマシンを初期状態に戻し、画像データの入力開始に対応できるように備える
  - conf_reg:
    - ソフトウェアプログラム側に、データの書き込みが行われていない期間を伝える信号(CBLANK)を設定する
    - ソフトウェアプログラムは、その期間に合わせて、書込みのオンオフ(DISPON)を切り替えたり、書込み先の先頭アドレス(DISPADDR)を切り替えたりする
  - buf_fifo:
    - 内部に格納されているデータのクリアを行う


blank_flagのブロック図:
![blank_flag_func_02.drawio.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3588818/94fc3a78-d41c-a88d-b5c5-8a298717bdad.png)
- 赤色の実線で囲われている部分がblank_flagの部分

## 機能一覧

先ほどのブロック図の中で分けられた部分は、それぞれ以下に示す役割を担う。
- (1) VSYNC取り込み
  - PCLK駆動のモジュール(input_ov7670)から入力されてくるVSYNC_r信号をm_clockで同期化
  - 同期化を行った信号は3bitのFFの最下位bitに格納
  - 格納されるたび順番にbitシフトされていく
  - 取込んだ信号はFIFOのデータクリア信号に出力する
- (2) エッジ検出
  - VSYNC取り込み部のFFを用いて立上りエッジの検出を行う
  - 検出した結果を以下に出力する
    - 空白期間としてみなし、conf_regにSETCBLNK信号を出力
    - 1画面開始に備え、sdram_transferをステートマシンを初期状態に戻す信号を出力

## 実装ファイル

<details>
<summary>長いので折り畳み</summary>

ヘッダファイル
```nsl:blank_flag.nsh
#ifndef _BLANK_FLAG_NSH
#define _BLANK_FLAG_NSH

declare blank_flag {
    input VSYNC_r;
    func_out ON_VSYNC();
    output CLRFIFO;
}

#endif // _BLANK_FLAG_NSH
```

モジュール
```nsl:blank_flag.nsl
#include "blank_flag.nsh"

module blank_flag {
	reg vsync_ff[3] = 0;
    vsync_ff := {vsync_ff[1:0], VSYNC_r};
    // エッジ検出
    if(vsync_ff[2:1] == 2'b01) ON_VSYNC();
    // FIFOクリア信号
    CLRFIFO = vsync_ff[2];
}
```

</details>


# conf_regモジュール

概要
- PIOから入力されてくる信号をレジスタに格納
  - 他のサブモジュールに出力する
- 空白期間を示すCBLANKフラグの値を記憶
  - PIOに出力する

conf_regのブロック図
![conf_reg_func_05.drawio.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3588818/95ab5c65-33ed-23b8-14c2-760660de3b0b.png)
- 赤色の実線で囲われている部分がconf_regの部分

## 機能一覧

先ほどのブロック図の中で分けられた部分は、それぞれ以下に示す役割を担う。
- (1) キャプチャオン設定レジスタ
  - PIOから入力されてきたキャプチャオンの情報をFFに格納し、他モジュールへ出力する
- (2) 転送先アドレス設定レジスタ
  - PIOから入力されてきた転送先アドレスの情報をFFに格納し、sdram_transferへ出力する
- (3) CBLANKレジスタ
  - 空白期間を示すCBLANKフラグの管理を行う
    - FFを用いて保持されている値はCBLANKとしてPIOに出力
  - blank_flagモジュールからブランクフラグセット信号が入力
    - 値が1にする
  - PIOからCLRCBLNKの入力を受け取る
    - 値が0にする
  - 何もないとき、値は保持される

## 実装ファイル

<details>
<summary>長いので折り畳み</summary>

ヘッダファイル
```nsl:conf_reg.nsh
#ifndef _CONF_REG_NSH
#define _CONF_REG_NSH

declare conf_reg {
    input CLRCBLNK;
    input CAPON;
    input CAPADDR[26];
    output CBLANK;
    
    func_in SETCBLNK;
    output CAPON_r;
    output CAPADDR_r[26];
}

#endif // _CONF_REG_NSH
```

モジュール
```nsl:conf_reg.nsl
#include "conf_reg.nsh"

module conf_reg {
    reg capon_ff=0;
    reg capaddr_ff[26]=0;
    reg cblank_ff=0;

    capon_ff := CAPON;
    CAPON_r = capon_ff;

    capaddr_ff := CAPADDR;
    CAPADDR_r = capaddr_ff;

    alt{
        CLRCBLNK:{
            cblank_ff := 0;
        }
        SETCBLNK:{
            cblank_ff := 1;
        }
    }

    CBLANK = cblank_ff;
}

```

</details>

# xclk_genモジュール

概要
- 100MHzのクロック入力m_clockから、25MHzのXCLKを生成
  - CMOSカメラ(OV7670)に出力する
- リセット解除後、m_clockが立ち上がるたびに分周カウンタがカウントアップ
  - 分周カウンタは2bitのレジスタで構成
    - 上位bitをXCLK信号として出力する

xclk_genのブロック図
![xclk_gen_func_01.drawio.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3588818/38e85178-bb04-11e5-48c2-c6398cef2aa6.png)
- 赤色の実線で囲われている部分がxclk_genの部分

## 実装ファイル

<details>
<summary>長いので折り畳み</summary>

ヘッダファイル
```nsl:xclk_gen.nsh
#ifndef _XCLK_GEN_NSH
#define _XCLK_GEN_NSH

declare xclk_gen {
	output XCLK;
}

#endif // _XCLK_GEN_NSH

```

モジュール
```nsl:xclk_gen.nsl
#include "xclk_gen.nsh"

module xclk_gen {
	reg prs_cnt_4[2] = 0;
    prs_cnt_4++;
    XCLK = prs_cnt_4[1];
}
```

</details>


## buf_fifoモジュール

概要
- 1024段、入出力24bitのFIFO
- input_cv7670モジュールとsdram_transferモジュールの間で画像データのバッファとして挟む。

このモジュールはIP Catalogを用いて作成する。

#### 導入手順
注意：IP Catalog でモジュールを作成する場合、FPGAボードによって作成手順が異なる。以下は Cyclone V 準拠のもの。
- (1) Quartus Prime を起動し、IP Catalogから以下の順で選択する。
  -  `Basic Functions` > `On Chip Memory` > `FIFO`
![fifo_setting01.dio.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3588818/82a5fa3a-5f33-aa3f-b44b-5f5e883f9292.png)
- (2) filetypeに`Verilog`, filenameに`保存先のフォルダのパス/buf_fifo.v`と指定する
![fifo_setting02.dio.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3588818/2d83072a-44f8-428b-4957-4a2dd088f487.png)
- (3) bit幅:24,ワード数:1024と設定する
- (4) クロックは読み書きで別系統にするため`No, synchronize reading and writing ...`を選ぶ
![fifo_setting03.dio.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3588818/37423367-be74-442f-37c8-18a749b91f81.png)
- (5) Optimizationの設定は、デフォルトの`Minimal setting for unsychronized clocks ...`を設定する
![fifo_setting04.dio.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3588818/febe62be-07cb-84b2-c6ac-98a02faa2826.png)
- (6) フラグ類の設定はRead-sideの以下の項目にチェックを入れる。
  - usedw[]
  - Asynchronous clear
  - Add circuit to synchronize 'aclr' input with 'rdclk'
![fifo_setting05.dio.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3588818/cf3bce4e-f6ac-a680-f493-12e6d5ffa553.png)
- (7) 読み出しの設定は`Normal synchronous FIFO mode. ...`を選ぶ
- (8) その他の設定はデフォルトにする
![fifo_setting06.dio.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3588818/c41a695a-0d3f-a4e2-a76e-0907ea6fb86a.png)
- (9) `finish`を選択すると、qipファイルを追加するかどうか確認されるので`Yes`を選択
![fifo_setting07.dio.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3588818/7d87ce6e-ae53-c7b8-aa71-24b60111e648.png)

## 入出力宣言

- 入出力ポートを宣言したファイルを用意する
  - 生成したVerilogファイルをNSL側で参照できるようにする

<details>
<summary>ヘッダファイル</summary>

```
#ifndef _BUF_FIFO_NSH
#define _BUF_FIFO_NSH

declare buf_fifo interface {
	input  wrclk;
    input  wrreq;
    input  data[24];

    input  rdclk;
    input  rdreq;
    output q[24];
    
    output rdusedw[10];
    
    input  aclr;
}

#endif // _BUF_FIFO_NSH
```

</details>

# cap_ipモジュール(上位モジュール)
このモジュールが上位モジュールとなり、各インターフェイスやサブモジュールの信号を配線する。

配線接続図
![cap_ip_wiring_06.dio.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3588818/e06fab2f-5c48-ef97-897a-34d745676551.png)

## 実装ファイル

<details>
<summary>長いので折り畳み</summary>

ヘッダファイル
```
#ifndef _CAP_IP_NSH
#define _CAP_IP_NSH

#include "input_ov7670.nsh"
#include "sdram_transfer.nsh"
#include "blank_flag.nsh"
#include "conf_reg.nsh"
#include "xclk_gen.nsh"
#include "buf_fifo.nsh"

declare cap_ip interface {
	// システム
    input m_clock;
    input p_reset;

    // カメラキャプチャ(OV7670)
    output coe_cc_XCLK;
    input coe_cc_PCLK;
    input coe_cc_VSYNC;
    input coe_cc_HREF;
    input coe_cc_CAMDATA[8];

    //データ転送(Avalon-MM)
    input avm_waitrequest;
    output avm_address[26];
    output avm_write;
    output avm_writedata[32];
    output avm_burstcount[5];

    //モジュール設定(PIO)
    input coe_cm_CAPADDR[26];
    input coe_cm_CAPON;
    input coe_cm_CLRCBLNK;
    output coe_cm_CBLANK;
}

#endif // _CAP_IP_NSH

```

モジュール
```
#include "cap_ip.nsh"

module cap_ip {
    // インスタンス
    input_ov7670 u0_input_ov7670; 
    sdram_transfer u0_sdram_transfer; 
    blank_flag u0_blank_flag; 
    conf_reg u0_conf_reg; 
    xclk_gen u0_xclk_gen; 
    buf_fifo u0_buf_fifo; 
    
    // モジュール間接続信号
    
    //
    wire FIFO_WR;
    wire FIFO_IN[24];
    // 
    wire VSYNC_r;
    //
    wire FIFO_RD;
    wire DATA_CNT[10];
    wire FIFO_OUT[24];
    //
    // func_self ON_VSYNC;
    //
    wire CLRFIFO;
    // 
    wire CAPON_r;
    wire CAPADDR_r[26];
    
    // 各信号の配線

    // input_ov7670
    u0_input_ov7670.m_clock       = (coe_cc_PCLK);
    u0_input_ov7670.p_reset       = (p_reset);
    u0_input_ov7670.VSYNC         = (coe_cc_VSYNC);
    u0_input_ov7670.HREF          = (coe_cc_HREF);
    u0_input_ov7670.CAMDATA       = (coe_cc_CAMDATA);

    VSYNC_r                       = (u0_input_ov7670.VSYNC_r);   
    
    u0_input_ov7670.CAPON         = (CAPON_r);
    
    FIFO_WR                       = (u0_input_ov7670.FIFO_WR);
    FIFO_IN                       = (u0_input_ov7670.FIFO_IN);

    // sdram_transfer
    u0_sdram_transfer.m_clock     = (m_clock);
    u0_sdram_transfer.p_reset     = (p_reset);
    FIFO_RD                       = (u0_sdram_transfer.FIFO_RD);
    u0_sdram_transfer.DATA_CNT    = (DATA_CNT);
    u0_sdram_transfer.FIFO_OUT    = (FIFO_OUT);
    u0_sdram_transfer.CAPON       = (CAPON_r);
    u0_sdram_transfer.CAPADDR     = (CAPADDR_r);
    u0_sdram_transfer.waitrequest = (avm_waitrequest);
    avm_write                     = (u0_sdram_transfer.write);
    avm_burstcount                = (u0_sdram_transfer.burstcount);
    avm_address                   = (u0_sdram_transfer.address);
    avm_writedata                 = (u0_sdram_transfer.writedata);

    // blank_flag
    u0_blank_flag.m_clock         = (m_clock);
    u0_blank_flag.p_reset         = (p_reset);
    u0_blank_flag.VSYNC_r         = (VSYNC_r);
    CLRFIFO                       = (u0_blank_flag.CLRFIFO);
    func u0_blank_flag.ON_VSYNC{
        u0_sdram_transfer.SET_HALT();
        u0_conf_reg.SETCBLNK();
    }

    // conf_reg
    u0_conf_reg.m_clock           = (m_clock);
    u0_conf_reg.p_reset           = (p_reset);
    
    u0_conf_reg.CLRCBLNK          = (coe_cm_CLRCBLNK);
    u0_conf_reg.CAPON             = (coe_cm_CAPON);
    u0_conf_reg.CAPADDR           = (coe_cm_CAPADDR);
    coe_cm_CBLANK                 = (u0_conf_reg.CBLANK);
    
    // u0_conf_reg.SETCBLNK          = (ON_VSYNC);
    CAPON_r                       = (u0_conf_reg.CAPON_r);
    CAPADDR_r                     = (u0_conf_reg.CAPADDR_r);


    // xclk_gen
    u0_xclk_gen.m_clock           = (m_clock);
    u0_xclk_gen.p_reset           = (p_reset);
    coe_cc_XCLK                   = (u0_xclk_gen.XCLK);

    // buf_fifo
    u0_buf_fifo.wrclk             = (coe_cc_PCLK);
    u0_buf_fifo.wrreq             = (FIFO_WR);
    u0_buf_fifo.data              = (FIFO_IN);
    u0_buf_fifo.rdclk             = (m_clock);
    u0_buf_fifo.rdreq             = (FIFO_RD);
    FIFO_OUT                      = (u0_buf_fifo.q);
    DATA_CNT                      = (u0_buf_fifo.rdusedw);
    u0_buf_fifo.aclr              = (CLRFIFO);

}

```

</details>



# まとめ

今回はカメラキャプチャ回路のcap_ipモジュールを作成した
- CMOSカメラから入力されてくる画像データを取込みFIFOに格納
- バースト転送機能を用い、FIFOに格納されている画像データをSDRAMに転送す
- 垂直同期信号を受取り、ソフトウェアプログラム側に、
データの書き込みが行われていない期間を伝える信号(CBLANK)を作成
- PIOからの設定値をレジスタに格納し、各モジュールに伝える
- CMOSカメラ(OV7670)に供給するクロック信号を分周カウンタによって生成
- 画像データの一時保存として格納するFIFOをIP Catalogで作成

前回と今回でSCCB通信回路とカメラキャプチャ回路を実装した。
- 次回は、プログラムの作成と、モジュールを組合わせ、全体的なシステムの構築していく