« 2016年11月 | トップページ | 2017年1月 »

2016年12月の記事

Vivado Constraints Wizardによるクロック・入出力制約の作成

前回のポストで、ZYBOを使って、OV7670カメラのVGA画像が取り込めるようになりましたが、制約条件が未設定でした。VivadoのConstraints Wizardを使うことによってクロックと入出力の制約条件を設定して、Unconstraintsの箇所を消すことができました。制約条件を設定することによって画質が向上したため、回路を正しく動作させるためには制約条件の設定は必須なんだと分かりました。

以下に、Constraints Wizardを使った制約条件の設定方法を記載します。

操作手順

Constraints WizardはVivadoのFlow Navigatorから起動でき、SynthesisとImplementationの両方から起動できます。一旦合成・配置を行なった後だと、どちらから起動しても作成される制約ファイルの内容は同じになるようです。

Constraints Wizard

Wizardを起動します。カメラのPCLKをFCLKで同期化したpclk_sと、HLSとのインタフェースに使っているap_doneが、Generated Clock(ユーザー定義のクロック)の候補として表示されました。ソースクロック(FCLK)の分周率を指定する形式となっており、実際のクロック周波数とは同じになりませんが、ここでは4を指定しました。pclk_sは24MHzなので、100MHz ÷ 4がそれに近い値ということで。

当初はpclk_sの制約条件を”create_clock”コマンドで作成しようとしたのですが、create_clockはプライマリークロックの定義となっているため、create_clockを使うとImplematation時にタイミング違反が発生してしまいます。このようなケースでは、”create_generated_clock”を使うのが正しいのだということが分かりました。

Constraints Wizard-2

Constraints Wizard-3

次に入力遅延を設定します。入力遅延はリファレンスクロック(ここではFCLK)の立ち上がりに対する遅延ということなので、これも実態(PCLKの立ち上がりに対して、実際のデーターはセットアップ時間前に有効になっている)とは異なりますが、1〜2 nsの遅延を指定。

Constraints Wizard input delay

続けて、出力遅延を設定。こちらは、25MHzのVGAクロックの立ち上がりに対するセットアップ・ホールド遅延の設定になります。

Constraints Wizard output delay

次に、非同期クロックドメインを指定する画面が表示されます。下段に3つのエントリが表示されていますが、Non-recommendedということなのでチェックはしていません。この項目にチェックを入れると、"set_clock_groups -asynchronous “の制約が生成されるのですが、オブジェクトに指定されるクロック名が認識できない旨のCritical Waringが合成時に出てしまいました。

20161211 Constraints Wizard Async Clock

最後にWizardを完了。

Constraints Wizard done

以下のエントリが制約ファイルに追加されました。 

create_generated_clock -name ov7670_vram_sys_i/MemRead_0/inst/ap_done -source [get_pins {ov7670_vram_sys_i/processing_system7_0/inst/PS7_i/FCLKCLK[0]}] -divide_by 4 [get_pins {ov7670_vram_sys_i/MemRead_0/inst/ap_CS_fsm_reg[10]/Q}]
create_generated_clock -name ov7670_vram_sys_i/OV7670_camera_hs_0/inst/pclk_s -source [get_pins {ov7670_vram_sys_i/processing_system7_0/inst/PS7_i/FCLKCLK[0]}] -divide_by 4 [get_pins ov7670_vram_sys_i/OV7670_camera_hs_0/inst/pclk_s_reg/Q]
create_clock -period 40.000 -name VIRTUAL_ov7670_vram_sys_i/OV7670_camera_hs_0/inst/pclk_s -waveform {0.000 20.000}
create_clock -period 40.000 -name VIRTUAL_clk25_ov7670_vram_sys_clk_wiz_0_0 -waveform {-3.333 16.667}
set_input_delay -clock [get_clocks clk_fpga_0] -min -add_delay 2.000 [get_ports {data[*]}]
set_input_delay -clock [get_clocks clk_fpga_0] -max -add_delay 3.000 [get_ports {data[*]}]
set_input_delay -clock [get_clocks clk_fpga_0] -min -add_delay 2.000 [get_ports href]
set_input_delay -clock [get_clocks clk_fpga_0] -max -add_delay 3.000 [get_ports href]
set_input_delay -clock [get_clocks clk_fpga_0] -min -add_delay 2.000 [get_ports pclk]
set_input_delay -clock [get_clocks clk_fpga_0] -max -add_delay 3.000 [get_ports pclk]
set_input_delay -clock [get_clocks VIRTUAL_ov7670_vram_sys_i/OV7670_camera_hs_0/inst/pclk_s] -min -add_delay 2.000 [get_ports vsync]
set_input_delay -clock [get_clocks VIRTUAL_ov7670_vram_sys_i/OV7670_camera_hs_0/inst/pclk_s] -max -add_delay 3.000 [get_ports vsync]
set_output_delay -clock [get_clocks VIRTUAL_clk25_ov7670_vram_sys_clk_wiz_0_0] -min -add_delay -1.000 [get_ports {vo_b_data[*]}]
set_output_delay -clock [get_clocks VIRTUAL_clk25_ov7670_vram_sys_clk_wiz_0_0] -max -add_delay 3.000 [get_ports {vo_b_data[*]}]
set_output_delay -clock [get_clocks VIRTUAL_clk25_ov7670_vram_sys_clk_wiz_0_0] -min -add_delay -1.000 [get_ports {vo_g_data[*]}]
set_output_delay -clock [get_clocks VIRTUAL_clk25_ov7670_vram_sys_clk_wiz_0_0] -max -add_delay 3.000 [get_ports {vo_g_data[*]}]
set_output_delay -clock [get_clocks VIRTUAL_clk25_ov7670_vram_sys_clk_wiz_0_0] -min -add_delay -1.000 [get_ports {vo_r_data[*]}]
set_output_delay -clock [get_clocks VIRTUAL_clk25_ov7670_vram_sys_clk_wiz_0_0] -max -add_delay 3.000 [get_ports {vo_r_data[*]}]
set_output_delay -clock [get_clocks VIRTUAL_clk25_ov7670_vram_sys_clk_wiz_0_0] -min -add_delay -1.000 [get_ports vo_hsync]
set_output_delay -clock [get_clocks VIRTUAL_clk25_ov7670_vram_sys_clk_wiz_0_0] -max -add_delay 3.000 [get_ports vo_hsync]
set_output_delay -clock [get_clocks VIRTUAL_clk25_ov7670_vram_sys_clk_wiz_0_0] -min -add_delay -1.000 [get_ports vo_vsync]
set_output_delay -clock [get_clocks VIRTUAL_clk25_ov7670_vram_sys_clk_wiz_0_0] -max -add_delay 3.000 [get_ports vo_vsync]

合成・インプリメンテーションを実行すると、Unconstainted Pathがなくなりました。

Timing Summary

Clock Interactionを表示するとまだUnsafeなクロック乗せ変えパスが表示されています。WizardのAsynchronous CDCをチェックして赤がついているクロック間を非同期にするとUnsafeの表示を消すことができるのですが、先に示したように、Wizardが生成する制約エントリがうまく認識されないのでこのままとしています。 

Constraints Wizard clock interaction

制約条件を設定することによって、タイミング条件はかなり改善されたようです。入力側の信号がFPGAのFCLKに同期しているわけではないため(実態はカメラ側のPCLKに同期)、本当にこれでよいのかはまだよくわかっていないのですが。

結果

制約条件を加えて作成したBitstreamを使ってカメラを立ち上げると、以前に比べて画質がかなり向上しました。制約条件を入れる前は画像にジャギーが目立っていたのですが、制約条件を設定した後はジャギーがなくなりました。

<制約条件設定前の画像>

Constraints Wizard before

<制約条件設定後の画像>

Constraints Wizard after

制約条件が未設定の状態では回路が正しく動いていなかったことが分かりました。FPGAを使った回路の設計では、制約条件の設定やタイミング・クロージャーは必須事項なのだとよく分かりました。

参考資料

ZYBOでOV7670カメラモジュールのVGA画像を表示する

前回行った、OV7670カメラモジュールを使ったQVGA画像の表示を拡張して、VGA画像を表示できるようにしてみました。まだ不完全な部分がありますが、やったことを書いてみます。

全体構想

VGA(640 x 480) x 16bitカラーの画像を扱うためには、600KBのVRAM容量が必要となり、QVGAのようにFPGAのBRAMには格納できないため、ZYBOのPS側に搭載されているDDR3 RAMをVRAMとして使用する必要があります。今回はPL(FPGA部)からDDR3 RAMへのアクセスを行うことがテーマでした。

やり方は以下の3通りが考えられますが、③のAXI-HPを使ったメモリアクセスを使いました。

  1. AXIGPポート経由でアクセス(32bit幅)
  2. ZYNQ DMAエンジンを使ってDMA転送
  3. AXIHPポート経由でアクセス(32/64bit幅)

AXIHPポートを使ったDDR3メモリアクセスのイメージを下記に示します。データー転送側がAXI Masterデバイスとなります。

AXIHP

DDR3アクセスのために、AXI Masterプロトコルを喋るモジュールを作る必要がありますが、HLSで作るのが一番楽だと考え、以下のような構成としました。(インタフェースポートの属性にm_axiを指定するだけであとはHLSがAXI Masterの処理を行なってくれるため)

System_Design

Cameraからのデータ受信モジュールとVGA IFはクロックに従ったデーターの送受信が必要なため、HLSではなくVerilogで作成しています(前回作ったモジュールを拡張)。Verilog - HLS間のインタフェースはAXIS(Stream)を使うことも考えたのですが、どのタイミングでAXISのデーターの切れ目(tvalid)をHLSに伝えればよいかがよく分からなかったため、ピクセル座標をap_hs(ハンドシェーク)で渡すインタフェースとしました。当初はap_hsもそれほど苦労せずに作れるだろうと思ったのですが、ap_hsを動かすのに思いっきりハマりました。

今回の構成では、カメラからのデータの取り込みはOV7670のPCLK(24MHz)に同期する必要がありますが、メモリアクセス部分はFPGA PLに供給されている100MHzのシステムクロック(FCLK)で動作するため、クロックドメインまたがり(Clock Domain Crossing: CDC)の問題に直面し、このせいで(だと思うのですが)当初Cameraデーター受信モジュールが期待通りに動いてくれずかなり悩みました。あまりスマートでないように思うのですが、カメラ側の24MHzクロックをFCLKに同期化することで対処しています。

各モジュールのコード

HLSで書いたMemory Write / Memory Readジュールのコードは以下の通りです。

<Memory Write> 

#include <ap_int.h>
#include <stdint.h>

#define MAXLINE		480
#define MAXCOL		640

void MemWrite(ap_uint line, ap_uint col, uint16_t data, uint64_t *VRAM)
{
// depth 1280byte of lineBuff size is too big (got SIGSEGV fault in C/RTL co-sim),
// but 160 words(64bit) is too small
#pragma HLS INTERFACE m_axi depth=640 port=VRAM offset=direct bundle=VRAMW
#pragma HLS INTERFACE ap_hs port=col
#pragma HLS INTERFACE ap_hs port=line
#pragma HLS INTERFACE ap_hs port=data
#pragma HLS INTERFACE ap_ctrl_hs port=return

	static uint64_t lineBuff[2][MAXCOL/4];
// Force to make 2 line buffers in different BRAM
#pragma HLS ARRAY_PARTITION variable=lineBuff factor=2 dim=1
#pragma HLS RESOURCE variable=lineBuff core=RAM_1P_BRAM
	static uint64_t strData = 0;
	static ap_uint<2> mod = 0;
	ap_uint<1> cur;
	uint64_t inData;


// Data reception block
	cur    = line & 0x0001;
	inData = data;

	switch(mod) {
		case 0: strData = data;
				break;
		case 1: strData = strData | inData << 16;
				break;
		case 2: strData = strData | inData << 32;
				break;
		case 3: strData = strData | inData << 48;
				lineBuff[cur][col/4] = strData;
				break;
	}
	mod++;

	if (col == (MAXCOL - 1)) {
		// Data transfer block
		uint64_t* destPtr = &VRAM[line*MAXCOL/4];
		memcpy(destPtr, lineBuff[cur], MAXCOL*2);
		mod = 0;
		return;
	}

	return;
}

<Memory Read>

#include <ap_int.h>
#include <stdint.h>

#define MAXLINE		480
#define MAXCOL		640

uint16_t MemRead(ap_uint vga_line, ap_uint vga_col, uint64_t* VRAM)
{

#pragma HLS INTERFACE m_axi depth=640 port=VRAM offset=direct bundle=VRAMR
#pragma HLS INTERFACE ap_hs port=vga_line
#pragma HLS INTERFACE ap_hs port=vga_col
#pragma HLS INTERFACE ap_ctrl_hs port=return

	static uint64_t lineBuff[2][MAXCOL/4];
// Force to make 2 line buffers in different BRAM
#pragma HLS ARRAY_PARTITION variable=lineBuff factor=2 dim=1
#pragma HLS RESOURCE variable=lineBuff core=RAM_1P_BRAM

	static uint64_t rdData = 0;
	static ap_uint<2> mod = 0;
	ap_uint<1> cur;
	uint16_t pixcelData;

	cur = vga_line & 0x0001;

	// Transfer data during blanking period
	if (vga_col == 0) {
		uint64_t* srcPtr = &VRAM[vga_line*MAXCOL/4];
		memcpy(lineBuff[cur], srcPtr, MAXCOL*2);
		mod = 0;
	}

	switch(mod) {
		case 0: rdData = lineBuff[cur][vga_col/4];
				pixcelData = (uint16_t)rdData;
				break;
		case 1: pixcelData = (uint16_t)(rdData >> 16);
				break;
		case 2: pixcelData = (uint16_t)(rdData >> 32);
				break;
		case 3: pixcelData = (uint16_t)(rdData >> 48);
				break;
	}
	mod++;

	return pixcelData;
}

内容的には単純な処理で、MemoryWriteはCameraモジュールからCol/Lineの座標データと16bitカラーのデーターを受信し、4ピクセル分のデータを64bit長の配列に格納します。1 Line分(640 pixel)のデータを受信すると水平同期のブランキング期間を使ってデータをDDR3メモリーにバースト転送します。データー転送の時間を短くするためAXIバスは64bit長で動かしています(そのため、ピクセルデーターを64bit単位にパッキングしています)。VRAM portに64bit符号なし整数に対するポインターを(uint64_t *)指定して、インタフェースモードにm_axiを指定すると64bitモードで動いてくれました。

VRAMメモリのオフセット値はdirectを使ってBlock Designのconstantで指定しています(今回は、80_0000Hを使用)。

MemoryReadも同様で、水平同期のブランキング期間を最初に持ってきており、1 Line分のデータをブランキング期間にDDRメモリから読み込んで、VGA IFが要求するpixcel座標に対応したデーターを渡すようにしています。

HLSの合成結果を以下に示します。MemWriteは最小2クロックサイクルで動作し、DDRメモリーにデーター転送を行う際は169クロックかかることが分かります。MemReadは同様な処理を行なっているのですがLatencyが1クロックサイクル多く、1処理に3クロックサイクルを要しています。

MemWrite

MemRead

次に、OV7670カメラモジュールIF、VGA IFのVerilogコードを示します。以前のHDLコードは参考にしたブログのコードそのままなのですが、複数のレジスターを一つのalwaysブロックの中で操作していました。参考書として参照した「FPGAボードで学ぶ組込みシステム開発入門[Altera編] 」ではレジスタ毎に独立したalwaysブロックを使うスタイルの記述になっていたため、参考書のスタイルに改めました。生成される回路は異なると思いますが、alwaysブロックで複数レジスタを操作しないのがベストプラクティスな書き方なのかがまだよく分かっていません。

<OV7670カメラモジュールIF>

`timescale 1ns / 1ps
//////////////////////////////////////////////////////////////////////////////////
// Company:
// Engineer:  Kenshi Kamiya
//
// Create Date: 2016/10/11 15:00:13
// Design Name: ov7670_camera
// Module Name: ov7670_camera
// Project Name: ov7670 camera data capture and output data wht ap_hs
// Target Devices: Zybo
// Tool Versions: Vivado 2016.3
//
// Revision: 01
// Revision 0.01 - File Created
// Additional Comments:
//
//////////////////////////////////////////////////////////////////////////////////

/* This is a bit tricky href starts a pixel transfer that takes 3 cycles for first pixel
   then 2 cycles after 2nd pixcel

        Input | state after clock tick
         href | wr_hold   data_in           data_out     we   col     col_next
   cycle -1 x | xx      xxxxxxxxxxxxxxxx xxxxxxxxxxxxxxxx  x  xxxx      xxx
    cycle 0 1 | 00      xxxxxxxxRRRRRGGG xxxxxxxxxxxxxxxx  x  xxxx      col
    cycle 1 0 | 01      RRRRRGGGGGBBBBB  xxxxxxxxRRRRRGGG  x  col       col
    cycle 2 x | 10      GGGBBBBBxxxxxxxx RRRRRGGGGGGBBBBB  1  col       col+1
*/

module ov7670_camera_hs(
  input  wire clk,
  input  wire resetN,
  input  wire pclk,
  input  wire vsync,
  input  wire href,
  input  wire [7:0] data,
  output reg  [9:0] line,
  output reg  [9:0] col,
  output reg  [15:0] data_out,
  output wire  ap_start,
  input  wire ap_idle,
  input  wire ap_done,
  output wire  col_vld,
  output wire  line_vld,
  output wire  data_vld,
  input  wire col_ack,
  input  wire line_ack,
  input  wire data_ack
);

  reg [9:0] col_next;
  reg [9:0] line_next;
  reg [7:0]  data_s;
  reg [15:0] data_in;
  reg [1:0]  wr_hold;
  reg        we;
  reg [2:0]  state;
  reg [3:0] control;
  // Registers for synchronizer
  reg       pclk_d, pclk_s;
  reg       href_d, href_s;
  
  parameter IDLE=0, AP_START=1, OUT_VLD=2, OUT_ACK=3, AP_DONE=4, WAITE_NEXT=5;
  parameter MAXCOL=640, MAXLINE=480;
  
  // Synchronizer for pclk
  always @(posedge clk)
    begin
      pclk_d <= pclk;
      pclk_s <= pclk_d;
    end

  // Synchronizer for href
  always @(posedge clk)
    begin
      href_d <= href;
      href_s <= href_d;
    end
  
  // Synchronizer for input data
  always @(posedge clk)
    begin
      data_s <= data;
    end
  
  // FSM for ap_hs protocol
  always @(posedge clk)
    if (resetN == 0)
      state <= IDLE;
    else
      begin
        case(state)
          IDLE:     begin
                      if (we == 1)
                        begin
                          state <= AP_START;
                        end
                      else
                        begin
                          state <= IDLE;
                        end
                    end
          AP_START: begin
                      state <= OUT_VLD;
                    end
          OUT_VLD:  begin
                      state <= OUT_ACK;
                    end
          OUT_ACK:  begin
                      // ap_ack retun immediatry, so no chcke ap_ack
                      // To catch when ap_done return one clclk after ap_ack
                      if (ap_done == 1)
                        begin
                          state <= WAITE_NEXT;
                        end
                      else
                        begin
                          state <= AP_DONE;
                        end
                    end
          AP_DONE:  begin
                      if (ap_done == 1)
                        begin
                          state <= WAITE_NEXT;
                        end
                      else
                        begin
                          state <= AP_DONE;
                        end
                    end
          WAITE_NEXT: begin
                        // Wait to end current WE cycle
                        if(we == 1)
                          begin
                            state <= WAITE_NEXT;
                          end
                        else
                          begin
                            state <= IDLE;
                          end
                      end
          default:    begin
                          state <= IDLE;
                      end
        endcase
      end

  // Generate control signal
  always @(*)
    case(state)
      IDLE:     control = 4'b0000;
      AP_START: control = 4'b1000;
      OUT_VLD:  control = 4'b1111;
      OUT_ACK:  control = 4'b0000;
      AP_DONE:  control = 4'b0000;
      WAITE_NEXT: control = 4'b0000;
      default:  control = 4'b0000;
    endcase

  assign {ap_start, col_vld, line_vld, data_vld} = control;
  

  //  Camera data capture FSM
  always @(posedge pclk_s)
    begin
      if (vsync)
        wr_hold  <= 2'd0;
      else
        begin
          we <= wr_hold[1];
          wr_hold  <= {wr_hold[0],  (href_s & ~wr_hold[0])};
          data_out <= data_in;
          data_in  <= {data_in[7:0], data_s};
        end
    end

  always @(posedge pclk_s)
    begin
      if (vsync)
        begin
          col      <= 10'd0;
          col_next <= 10'd0;
        end
      else
        begin
          col <= col_next;
          if (wr_hold[1] == 1)
            begin
              col_next <= col_next + 1;
              if (col_next == MAXCOL - 1)
                 col_next  <= 10'd0;
            end
        end
    end

  always @(posedge pclk_s)
    begin
      if (vsync)
        begin
          line     <= 10'd0;
          line_next<= 10'd0;
        end
      else
        begin
          line <= line_next;
          if (wr_hold[1] == 1 && col_next == MAXCOL - 1)
              begin
                line_next <= line_next + 1;
                if (line_next == MAXLINE - 1)
                    line_next <= 10'd0;
              end
          end
        end

endmodule

<VGA IF> 

`timescale 1ns / 1ps
//////////////////////////////////////////////////////////////////////////////////
// Company:
// Engineer: Kenshi Kamiya
//
// Create Date: 2016/11/06 16:33:59
// Design Name: VGA Driver with ap_hs protocol
// Module Name: vga_hs
// Project Name: OV7670_VGA
// Target Devices: Zybo
// Tool Versions: Vivado 2016.3
// Description:
//
// Dependencies:
//
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
//
//////////////////////////////////////////////////////////////////////////////////

module vga_hs(
  input wire  clk,
  input  wire clk25,
  input  wire resetN,
  output reg  [4:0] vo_r_data,
  output reg  [5:0] vo_g_data,
  output reg  [4:0] vo_b_data,
  output reg  vo_hsync,
  output reg  vo_vsync,
  output reg [9:0] vga_line,
  output reg [9:0] vga_col,
  input  wire [15:0] frame_pixel,
  output wire ap_start,
  input  wire ap_idle,
  input  wire ap_done,
  output wire line_vld,
  output wire col_vld,
  input  wire line_ack,
  input  wire col_ack
  );
  
  parameter hRez = 640, hFrontPorch = 16, hSyncPluse = 96, hBackPorch = 48, hMaxCount = 800;
  parameter vRez = 480, vFrontPorch = 10, vSyncPluse = 2,  vBackPorch = 33, vMaxCount = 525;
  parameter hDispStart = hFrontPorch + hSyncPluse + hBackPorch;  // 160
  parameter vDispStart = vFrontPorch + vSyncPluse + vBackPorch;  // 45
  
  parameter IDLE=0, AP_START=1, OUT_VLD=2, AP_DONE=3, WAITE=4;
  
  reg  [9:0] hCounter;
  reg  [9:0] vCounter;
  reg  [9:0] cur_hCounter;
  reg   blank;
  reg  [2:0]  state;
  reg  [15:0] pixelData;
  reg  [2:0] control;
  wire cke;
  
  assign cke   = ~blank & clk25;

  // hCounter
  always @(posedge clk25)
    begin
      if (resetN == 0)
        hCounter <= 10'd0;
      else if (hCounter == (hMaxCount - 1))
        hCounter <= 10'd0;
      else
        hCounter <= hCounter + 1;
     end

  //vCounter
  always @(posedge clk25)
    begin
      if (resetN == 0)
        vCounter <= 10'd0;
      else if (hCounter == (hMaxCount - 1))
        if (vCounter == (vMaxCount - 1))
          vCounter <= 10'd0;
        else
          vCounter <= vCounter + 1;
    end

  // FSM for ap_hs protocol
  always @(posedge clk)
    if (resetN == 0)
      state <= 0;
    else
      case(state)
        IDLE:     begin   // State 0
                    if ( (cke && vga_col >= 1) || (vCounter >= vDispStart && hCounter == 11'd10))
                      state = AP_START;
                    else
                      state = IDLE;
                  end
        AP_START: begin   // State 1
                    state = OUT_VLD;
                  end
        OUT_VLD:  begin   // State 2
                    state = AP_DONE;
                  end
        AP_DONE:  begin   // State 3
                    // ap_ack retun immediately, so no chcke ap_ack
                    if (ap_done && cke)
                      state <= AP_START;    // Sthort cut to execute next cycle
                    else if (ap_done )
                      state = IDLE;
                    else if (ap_done && hCounter < hDispStart)
                      state = WAITE;
                    else
                      state= AP_DONE;
                  end
        WAITE:    begin   // State 4
                    // Waite to finish hBlanking in case read first block at line start
                    if (hCounter < hDispStart)
                      state = WAITE;
                    else
                      state = IDLE;
                  end
        default:  begin
                    state = IDLE;
                  end
      endcase

  always @(*)
    case(state)
      IDLE:     control = 3'b000;
      AP_START: control = 3'b100;
      OUT_VLD:  control = 3'b111;
      AP_DONE:  control = 3'b000;
      WAITE:    control = 3'b000;
      default   control = 4'b000;
    endcase

  assign  {ap_start, line_vld, col_vld} = control;
  
  // Generate HSYNC
  always @(posedge clk25)
  begin
    if (resetN == 0)
      vo_hsync <= 1;
    else if ((hCounter > hFrontPorch) && (hCounter <= hFrontPorch + hSyncPluse))
      vo_hsync <= 0;
    else
      vo_hsync <= 1;
  end

  // Generate VSYNC
  always @(posedge clk25)
  begin
    if (resetN == 0)
      vo_vsync <= 1;
    else if ((vCounter > vFrontPorch) && (vCounter <= vFrontPorch + vSyncPluse))
      vo_vsync <= 0;
    else
      vo_vsync <= 1;
  end
  
  // Generate blank signal
  always @(posedge clk25)
  begin
    if (resetN == 0)
      blank <= 1;
    else if ((vCounter >= vDispStart) && (vCounter < vMaxCount ) && (hCounter >= hDispStart) && (hCounter < hMaxCount) )
      blank <= 0;
    else
      blank <= 1;
  end

  // VGA column address
  always @(posedge clk25)
    begin
      if (resetN == 0)
        vga_col  <= 10'd0;
      else if (blank == 0)
       if (vga_col == hRez - 1)
          vga_col  <= 10'd0;
        else
          vga_col <= vga_col + 1;
  end

  // VGA line address
  always @(posedge clk25)
    begin
      if (resetN == 0)
        vga_line <= 10'd0;
      else if (blank == 0)
        if (vga_col == (hRez - 1))
          if (vga_line == (vRez -1))
            vga_line <= 10'd0;
          else
            vga_line <= vga_line +1;
  end

  // Generate color signal
  always @(posedge clk25)
    begin
      if (blank == 0)
        begin
          vo_r_data <= pixelData[15:11];
          vo_g_data <= pixelData[10:5];
          vo_b_data <= pixelData[4:0];
        end
      else
        begin
          vo_r_data <= 5'd0;
          vo_g_data <= 6'd0;
          vo_b_data <= 5'd0;
        end
  end

  always @(posedge ap_done)
    begin
      pixelData <= frame_pixel;
    end

endmodule

動作確認(シミュレーション)

Verilogで作成したCamera IFとHLSで作成したMemWriteが正しく動作するかの検証を行いました。特にap_hsプロトコルが期待通りに動くかの検証が必要でした。ただ、ov7670_camera_hsとMemWriteをBlock Designで繋いだだけではダメで、MemWriteをAXI-Slaveに繋いでやらないと動作してくれません。AXIのBFMが有償なのでシミュレーションができないとTwitterでぼやいていたら、@marsee101 さんからご自身で作成されたAXI Slave BFMのありかを教えていたき、そのBFMを使って以下のようなシミュレーション環境を作りました。

MemWrite_TsetBed

axi_slave_bfmが @marsee さんから拝借したAXI-SlaveのBFMです。おかげで、VerilogとHLSモジュールの連携をシミュレーションすることができて大変助かりました。Block DesignでMemWrite (HLS) とaxi_bfm_slaveを作成してauto connectを実行すると、自動的にAXI-InterconnectやProsessor System Resetモジュールもインスタンス化され接続してくれました。

当初のコードでは、ov7670_camera_hsの164行目に相当する、OV7670からのデーター受信処理で、OV7670からのpclkをそのまま使ってpclkに同期したwe信号(ov7670_camera_hs 170行目)がHighになることをトリガーにして、ap_hsをスタートして(ov7670_camera_hs 94行目)MemWrite (HLS)にデーター転送を行おうとしました。このコードでは、シミュレーションではBehavioral Simulation、Post-Synthesis Simulation共に正常に動作しているように見えたのですが、実機ではどうしてもweがHighになった際にap_startパルスが出ず、MemWriteを起動できない問題に遭遇しました。

この問題の解決にかなり時間を要したのですが、we信号はpclkに同期して生成しているが、ap_hsプロトコルはpclkとは非同期の100MHz FCLKに同期して動かしているため、クロックドメインの違いによって問題が発生しているのではないかと考え、pclkを67行目に示すシンクロナイザーを通してFCLKに同期化してやると正常に動作するようになりました。

クロックドメインまたがり(CDC: Clock Domain Crossing)の処理として、入力クロックをシステムクロックで打ち直すのが正しい処理なのか自信がないのですが、まずは動いているのでよしとしています。Post-Synthesis Timing Simulationを行なった際の波形を以下に示します。

MemWrite Simulation-1

href信号が有効になると画像データーの取り込み処理を始めて、weがHighになった次のシステムクロック(clk)の立ち上がりエッジでap_startを出し、3クロックサイクル後にap_doneが返っています。ap_startの信号を組み合わせ回路で生成しているため、信号にグリッチが出ています。レジスターを通すことでグリッチをなくすことができるのですが、ap_startの立ち上がりが1クロック遅延します。Camera IFではタイミングに余裕があるためレジスターを入れてもよいのですが、後で説明するVGA IF回路ではタイミングの余裕がなく、制御信号生成にレジスターを入れることができなかったため、処理ロジックを共通化する意味で、信号のグリッチには目をつぶっています(動作には問題なさそうですので)。

また、これまでFSMを書く際は、状態遷移(next_stateに遷移する条件)を組み合わせ回路(always @*)で記述して、状態を進める処理を以下のような順序回路で記述していました: 

always @(posedge clk)
  begin
    if (resetN == 0)
      cur_state <= IDLE;
    else
      cur_state <= next_state;
  end 

このような組み合わせ回路と状態を進める順序回路の組み合わせでFSMを記述すると、weを検出した際のap_start起動に1クロックの遅延が発生します。VGA IF回路ではap_startの遅延を最小限にしたかったため、今回は状態遷移自体を順序回路のスタイルで記述しています。

640 column分のデータを受信すると(colアドレス = 0x27Fになると)MemWrite (HLS)がDDRメモリーにデーター転送処理を始めるためap_doneが直ぐに返らずに、169クロック後にap_doneが返ってきます。

MemWrite Simulation-2

MemWrite Simulation 3

VGA IFに関しても同様の試験環境を作ってシミュレーションを行なっています。

MemRead TestBed

OV7670 Camera IFの場合は24MHzサイクルの2クロック毎にMemWriteモジュールにデーターを転送できればよいため比較的タイミングに余裕があったのですが、VGA IFでは25MHzのVGA pixcelクロック毎にMemReadモジュールから画像データーをもらう必要があるため、タイミングマージンがギリギリになりました。以下にPost-Synthesis Timing Simulationを行なった際の波形を示します。VGA IFではvga_hsの59行目でvga pixcelクロック(25MHz)と有効画素範囲(ブランキング期間以外)のANDを取ってこのcke信号がHighになることをトリガーにしてMemReadモジュールに対してap_startを出しています。

MemRead Simulation

ap_startを最短で検出できるよう、VGA clockをMMCMで生成する際に-30度位相シフトして、VGA clk(clk25)がHighになった直後のシステムクロック(clk)の立ち上がりでap_startが出せるようにしています。シミュレーションでは次のVGAクロックサイクルが始まる直前ギリギリにap_doneが返っていますが、やはりpixelデーター毎にハンドシェークを行うap_hsを使うのはタイミング的に厳しいので、このようなケースではAXIS(ストリーミング)を使った方がよいように思われます。

全体の構成

全モジュールを結合した最終的なBlock Designを以下に示します。

System Block Design

Cameraモジュールでap_startが出ない問題にかなり悩まされましたが、なんとか動くようになりました。

残課題

以下の課題が残っており、まだ完全とは言えません。

1) 画像の左側にノイズが出る

OV7670カメラの水平方向の有効画素範囲の設定の問題だと思うのですが、該当レジスタ値(HSTART, HSTOP, HREF)を変更して表示ウインドウを右にシフトさせようとしたのですが、デフォルト値以外の値に設定すると、href信号の出力パターン(パルス幅)が大きく変わってしまい画像が取り込めなくなるため、現状未解決です。

Lift side noize

2) タイミング制約の問題

ov7670_camera_hsモジュールで、システムクロックに同期化したpclk_sを使って生成している信号やレジスタが、合成後unconstrainedになっています。unconstrainedの警告を消すために、試しにpclk-sにクロック制約を入れると、今度は合成時にhold/setupマージン不足(negative slack)の状態になるクロックパスが多数発生してしまうため、クロック制約は外しています。タイミング制約の使い方やタイミング条件の正しい設計はもっと勉強をしないといかんです。

クロック制約:create_clock -period 41.666 -name pclk_s -waveform {0.000 20.833} [get_pins ov7670_vram_sys_i/OV7670_camera_hs_0/inst/pclk_s_reg/Q]

Unconstrained

3) Unknown 1-bit CDC  circuitry

クロック載せ替えで問題が出ていたので、Report CDCを行うと、1-bit unknown CDC circuitryのパスが多数検出されます。

Unkonown 1bit CDC

Unsafeではないのですが、1-bit unknown CDCが出ている回路を表示してみると、以下のように、システムクロックに同期したリセット信号と、同じくシステムクロックに同期したVGAクロックが同じFFに入力されている2つの信号の関係性で、unknown 1-bit CDCが発生しているように見えます。試しに、destination側のレジスタに”ASYNC_REG”プロパティーを設定したりしたのですが変化がありませんでした。クロック載せ替えの最適化もまだよく分かっていない部分です。

Unkonown CDC 2

参考資料

« 2016年11月 | トップページ | 2017年1月 »

2023年6月
        1 2 3
4 5 6 7 8 9 10
11 12 13 14 15 16 17
18 19 20 21 22 23 24
25 26 27 28 29 30  
無料ブログはココログ