Verylで作るCPU
Star

第8章
CPUの合成

これまでの章では、RV64IのCPUを作成してパイプライン化しました。今までは動作確認とテストはシミュレータで行っていましたが、本章では実機(FPGA)でCPUを動かします。

PYNQ-Z1

図8.1: PYNQ-Z1

8.1 FPGAとは何か?

集積回路を製造するには手間と時間とお金が必要です。FPGAを使うと、少しの手間と少しの時間、安価に集積回路の実現をお試しできます。

FPGA(Field Programmable Gate Array)は、任意の論理回路を実現できる集積回路のことです。ハードウェア記述言語で設計した論理回路をFPGA上に設定することで、実際に集積回路を製造しなくても実機で論理回路を再現できます。

「任意の論理回路を実現できる集積回路」は、主にプロダクトターム方式、またはルックアップ・テーブル方式で構成されています。本書ではルックアップ・テーブル(Lookup Table, LUT)方式のFPGAを利用します。

表8.1: 真理値表の例

XYA
000
011
101
110
<span class="tableref"><a href="./05b-synth.html#lut_sample_truth">表8.1</a></span>を実現するLUT

図8.2: 表8.1を実現するLUT

LUTとは、真理値表を記憶素子に保存しておいて、入力によって記憶された真理値を選択して出力する回路のことです。例えば、2つの入力XYを受け取ってAを出力する論理回路(表8.1)は、図8.2の回路で実現できます。ここでマルチプレクサ(multiplexer, MUX)とは、複数の入力を選択信号によって選択して出力する回路のことです。

図8.2では、記憶素子のデータをYによって選択し、さらにXによって選択することで2入力1出力の真理値表の論理回路を実現しています。入力がN個で出力がM個のLUTのことをN入力M出力LUTと呼びます。

ルックアップ・テーブル方式のFPGAは、多数のLUT、入出力装置、これらを相互接続するための配線によって構成されています。また、乗算回路やメモリなどの部品はFPGAよりも専用の回路で実現した方が良い*1ので、メモリや乗算回路の部品が内蔵されていることがあります。

[*1] 例えばメモリは同じパターンの論理回路の繰り返しで大きな面積を要します。メモリはよく利用される回路であるため、専用の回路を用意した方が空間的な効率が改善される上に、遅延が少なくなるという利点があります

本書では2つのFPGA(Tang Nano 9K、PYNQ-Z1)を使用して実機でCPUを動作させます。2024年11月12日時点ではどちらも秋月電子通商で入手できて、Tang Nano 9Kは3000円くらい、PYNQ-Z1は50000円くらいで入手できます。

8.2 LEDの制御

Tang Mega 138K ProのLED(6個)

図8.3: Tang Mega 138K ProのLED(6個)

大抵のFPGAボードにはLEDがついています。本章では2つのテストプログラム(LEDを点灯させる、LEDを点滅させる)によって、CPUの動作確認を行います。

LEDはトップモジュールのポートを経由して制御します(図8.4)。ポートとLEDの接続方法は合成系によって異なるため、それらの接続方法は後で考えます。CPUからLEDを制御するには、メモリ経由で制御する、CSRによって制御するなどの方法が考えられます。本書ではLEDを制御するためのCSRを実装して、CSRをトップモジュールのポートに接続することでLEDを制御します。

CSRのLED制御用レジスタがLEDに接続される

図8.4: CSRのLED制御用レジスタがLEDに接続される

8.2.1 CSRにLED制御用レジスタを実装する

RISC-VのCSRのアドレス空間には、読み込みと書き込みができるCSRを自由に定義できる場所(0x800から0x8FF)が用意されています*2。これの先頭アドレス0x800をLEDの制御用レジスタのアドレスとして実装を進めます。

[*2] The RISC-V Instruction Set Manual Volume II: Privileged Architecture version 20240411 Table 3. Allocation of RISC-V CSR address ranges.

まず、CsrAddr型にLED制御用レジスタのアドレスを追加します(リスト8.1)。

リスト8.1: リスト8.1: LEDの制御用レジスタのアドレスを追加する (csrunit.veryl)
    enum CsrAddr: logic<12> {
        MTVEC = 12'h305,
        MEPC = 12'h341,
        MCAUSE = 12'h342,
        LED = 12'h800,
    }

書き込みマスクはすべて書き込み可にします(リスト8.2)。

リスト8.2: リスト8.2: LEDの制御用レジスタの書き込みマスク (csrunit.veryl)
    const LED_WMASK   : UIntX = 'hffff_ffff_ffff_ffff;

LEDの制御用レジスタをcsrunitモジュールのポートに定義します。CSRの幅はUIntXです(リスト8.3)。

リスト8.3: リスト8.3: LEDの制御用レジスタを定義する (csrunit.veryl)
module csrunit (
    ...
    rdata      : output UIntX       ,
    raise_trap : output logic       ,
    trap_vector: output Addr        ,
    led        : output UIntX       ,
) {

rdatawmaskにLEDの制御用レジスタの値を割り当てます(リスト8.4)。

リスト8.4: リスト8.4: rdataとwmaskに値を割り当てる (csrunit.veryl)
    // read
    rdata = case csr_addr {
        CsrAddr::MTVEC : mtvec,
        CsrAddr::MEPC  : mepc,
        CsrAddr::MCAUSE: mcause,
        CsrAddr::LED   : led,
        default        : 'x,
    };
    // write
    wmask = case csr_addr {
        CsrAddr::MTVEC : MTVEC_WMASK,
        CsrAddr::MEPC  : MEPC_WMASK,
        CsrAddr::MCAUSE: MCAUSE_WMASK,
        CsrAddr::LED   : LED_WMASK,
        default        : 0,
    };

リセット時にLEDの制御用レジスタの値を0に設定します(リスト8.5)。

リスト8.5: リスト8.5: リセット値の設定 (csrunit.veryl)
    if_reset {
        mtvec  = 0;
        mepc   = 0;
        mcause = 0;
        led    = 0;
    } else {

LEDの制御用レジスタへの書き込み処理を実装します(リスト8.6)。

リスト8.6: リスト8.6: LEDの制御用レジスタへの書き込み (csrunit.veryl)
    case csr_addr {
        CsrAddr::MTVEC : mtvec  = wdata;
        CsrAddr::MEPC  : mepc   = wdata;
        CsrAddr::MCAUSE: mcause = wdata;
        CsrAddr::LED   : led    = wdata;
        default        : {}
    }

8.2.2 トップモジュールにLEDを制御するポートを実装する

LEDはトップモジュールのポートを経由して制御します(図8.4)。そのため、トップモジュールにLEDを制御するポートを作成して、csrunitのLEDの制御用レジスタの値を接続します(リスト8.7リスト8.8リスト8.9リスト8.10)。LEDの個数はFPGAによって異なるため、とりあえずXLEN(=64)ビットのポートを定義します。

リスト8.7: リスト8.7: coreモジュールにポートを追加する (core.veryl)
module core (
    clk     : input   clock                                    ,
    rst     : input   reset                                    ,
    i_membus: modport membus_if::<ILEN, XLEN>::master          ,
    d_membus: modport membus_if::<MEM_DATA_WIDTH, XLEN>::master,
    led     : output  UIntX                                    ,
リスト8.8: リスト8.8: csrunitモジュールのledポートと接続する (core.veryl)
    inst csru: csrunit (
        ...
        rdata      : csru_rdata      ,
        raise_trap : csru_raise_trap ,
        trap_vector: csru_trap_vector,
        led                          ,
    );
リスト8.9: リスト8.9: topモジュールにポートを追加する (top.veryl)
module top #(
    param MEMORY_FILEPATH_IS_ENV: bit    = 1                 ,
    param MEMORY_FILEPATH       : string = "MEMORY_FILE_PATH",
) (
    clk: input  clock,
    rst: input  reset,
    led: output UIntX,
    #[ifdef(TEST_MODE)]
    test_success: output bit,
) {
リスト8.10: リスト8.10: coreモジュールのledポートと接続する (top.veryl)
    inst c: core (
        clk       ,
        rst       ,
        i_membus  ,
        d_membus  ,
        led       ,
    );

CSRの読み書きによってLED制御用のポートを制御できるようになりました。

8.2.3 テストを作成する

LEDを点灯させるプログラム

LEDを点灯させるプログラムを作成します(リスト8.11リスト8.12)。CSRRWI命令で0x800に12('b01100)を書き込みます。

リスト8.11: リスト8.11: LEDを点灯させるプログラム (test/led.asm)
80065073 //  0: csrrwi x0, 0x800, 12
00000067 //  4: jal x0, 0
リスト8.12: リスト8.12: LEDを点灯させるプログラム (test/led.hex)
0000006780065073

LEDを点滅させるプログラム

LEDを点滅させるプログラムを作成します(リスト8.13リスト8.14)。これはちょっと複雑です。

リスト8.13: リスト8.13: LEDを点滅させるプログラム (test/led_counter.asm)
000f40b7 //  0: lui x1, 244
24008093 //  4: addi x1, x1, 576
00000113 //  8: addi x2, x0, 0
00110113 //  c: addi x2, x2, 1
fe209ee3 // 10: bne x1, x2, -4
800031f3 // 14: csrrc x3, 0x800, x0
00118193 // 18: addi x3, x3, 1
80019073 // 1c: csrrw x0, 0x800, x3
00000067 // 20: jalr x0, 0(x0)
00000067 // 24: jalr x0, 0(x0)
リスト8.14: リスト8.14: LEDを点滅させるプログラム (test/led_counter.hex)
24008093000f40b7
0011011300000113
800031f3fe209ee3
8001907300118193
0000006700000067

リスト8.13は次のように動作します。

  1. x1に1000000((244 << 12) + 576)を代入する
  2. x2に0を代入
  3. x2がx1と一致するまでx2に1を足し続ける
  4. LEDのCSRをx3に読み取り、1を足した値を書き込む
  5. 1 ~ 4を繰り返す

これにより、LEDの制御用レジスタは一定の時間ごとに012と値が変わっていきます。

8.3 FPGAへの合成① (Tang Nano 9K)

Tang Nano 9KをターゲットにCPUを合成します。使用するEDAのバージョンは次の通りです。

  • GOWIN FPGA Designer V1.9.10.03
  • Gowin Programmer Version 1.9.9

8.3.1 合成用のモジュールを作成する

Tang Nano 9KのLED(6個)

図8.5: Tang Nano 9KのLED(6個)

Tang Nano 9KにはLEDが6個実装されています(図8.5)。そのため、LEDの制御には6ビット必要です。それに対して、topモジュールのledポートは64ビットであるため、ビット幅が一致しません。

Tang Nano 9Kのためだけにtopモジュールのledポートのビット幅を変更すると柔軟性がなくなってしまうため、topモジュールの上位に合成用のモジュールを作成して調整します。

src/top_tang.verylを作成し、次のように記述します(リスト8.15)。top_tangモジュールのledポートは6ビットとして定義して、topモジュールのledポートの下位6ビットを接続しています。

リスト8.15: リスト8.15: Tang Nano 9K用の最上位モジュール (top_tang.veryl)
import eei::*;

module top_tang (
    clk: input  clock   ,
    rst: input     reset   ,
    led: output    logic<6>,
) {
    // CSRの下位ビットをLEDに接続する
    var led_top: UIntX;
    always_comb {
        led = led_top[5:0];
    }

    inst t: top #(
        MEMORY_FILEPATH_IS_ENV: 0,
        MEMORY_FILEPATH       : "",
    ) (
        clk,
        rst         ,
        led: led_top,
        #[ifdef(TEST_MODE)]
        test_success: _,
    );
}

8.3.2 プロジェクトを作成する

新規プロジェクトを作成します。GOWIN FPGA Designerを開いて、Quick StartのNew Project...を選択します。選択したら表示されるウィンドウでは、FPGA Design Projectを選択してOKを押してください(図8.6)。

FPGA Design Projectを選択する

図8.6: FPGA Design Projectを選択する

プロジェクト名と場所を指定します。プロジェクト名はtangnano9k、場所は好きな場所に指定してください(図8.7)。

プロジェクト名と場所の指定

図8.7: プロジェクト名と場所の指定

ターゲットのFPGAを選択します。GW1NR-LV9QN88PC6/I5を選択して、Nextを押してください(図8.8)。

ターゲットを選択する

図8.8: ターゲットを選択する

プロジェクトが作成されました(図8.9)。

プロジェクトが作成された

図8.9: プロジェクトが作成された

8.3.3 設定を変更する

プロジェクトのデフォルト設定ではSystemVerilogを利用できないため、設定を変更します。

ProjectのConfigurationから、設定画面を開きます(図8.10)。

設定画面を開く

図8.10: 設定画面を開く

SynthesizeのVerilog LanguageをSystem Verilog 2017に設定します。同じ画面でトップモジュール(Top Module/Entity)を設定できるため、core_top_tangを指定します(図8.11)。

設定を変更する

図8.11: 設定を変更する

8.3.4 設計ファイルを追加する

Verylのソースファイルをビルドして、生成されるファイルリスト(core.f)を利用して、生成されたSystemVerilogソースファイルをプロジェクトに追加します。

Gowin FPGA Programmerでファイルを追加するには、ウィンドウ下部のConsole画面でadd_fileを実行します。しかし、add_fileはファイルリストの読み込みに対応していないので、ファイルリストを読み込んでadd_fileを実行するスクリプトを作成します(リスト8.16)。

リスト8.16: リスト8.16: add_files.tcl
set file_list [open "ファイルリストのパス" r]
while {[gets $file_list line] != -1} {
    # skip blank or comment line
    if {[string trim $line] eq "" || [string index $line 0] eq "#"} {
        continue
    }
    # add file to project
    add_file $line
}
close $file_list

ウィンドウの下部にあるConsole画面で、次のコマンドを実行します(リスト8.17図8.12)。VerylをWSLで実行してGOWIN FPGA DesignerをWindowsで開いている場合、ファイルリスト内のパスをWindowsから参照できるパスに変更する必要があります。

リスト8.17: リスト8.17: Tclスクリプトを実行する
source add_files.tclのパス
コマンドを実行する

図8.12: コマンドを実行する

ソースファイルを追加できました(図8.13)。

ソースファイルの追加に成功した

図8.13: ソースファイルの追加に成功した

8.3.5 制約ファイルを作成する

物理制約

top_tangモジュールのclk、rst、ledポートを、それぞれTang Nano 9Kの水晶発振器、ボタン、LEDに接続します。接続の設定には物理制約ファイルを作成します。

新しくファイルを作成するので、プロジェクトを左クリックしてNew Fileを選択します(図8.14)。

New Fileを選択する

図8.14: New Fileを選択する

物理制約ファイルを選択します(図8.15)。

物理制約ファイルを選択する

図8.15: 物理制約ファイルを選択する

名前はtangnano9k.cstにします(図8.16)。

名前を設定する

図8.16: 名前を設定する

物理制約ファイルには、次のように記述します(リスト8.18)。

リスト8.18: リスト8.18: 物理制約ファイル (tangnano9k.cst)
// Clock and Reset
IO_LOC "clk" 52;
IO_PORT "clk" IO_TYPE=LVCMOS33 PULL_MODE=UP;
IO_LOC "rst" 4;
IO_PORT "rst" PULL_MODE=UP;

// LED
IO_LOC "led[5]" 16;
IO_LOC "led[4]" 15;
IO_LOC "led[3]" 14;
IO_LOC "led[2]" 13;
IO_LOC "led[1]" 11;
IO_LOC "led[0]" 10;

IO_PORT "led[5]" PULL_MODE=UP DRIVE=8;
IO_PORT "led[4]" PULL_MODE=UP DRIVE=8;
IO_PORT "led[3]" PULL_MODE=UP DRIVE=8;
IO_PORT "led[2]" PULL_MODE=UP DRIVE=8;
IO_PORT "led[1]" PULL_MODE=UP DRIVE=8;
IO_PORT "led[0]" PULL_MODE=UP DRIVE=8;

IO_LOCで接続する場所の名前を指定します。

場所の名前はTang Nano 9Kのデータシートで確認できます。例えば図8.17図8.18から、LEDは101113141516に割り当てられていることが分かります。また、LEDが負論理(1で消灯、0で点灯)であることが分かります。水晶発信器とボタンについても、データシートを見て確認してください。

LED

図8.17: LED

PIN10_IOL~の接続先

図8.18: PIN10_IOL~の接続先

タイミング制約

FPGAが何MHzで動くかをタイミング制約ファイルに記述します。

物理制約ファイルと同じようにAdd Fileを選択して、タイミング制約ファイルを作成します(図8.19)。

タイミング制約ファイルを選択する

図8.19: タイミング制約ファイルを選択する

名前はtiming.sdcにします(図8.20)。

名前を設定する

図8.20: 名前を設定する

タイミング制約ファイルには、次のように記述します(リスト8.19)。

リスト8.19: リスト8.19: タイミング制約ファイル (timing.sdc)
create_clock -name clk -period 37.037 -waveform {0 18.518} [get_ports {clk}]

Tang Nano 9Kの水晶発振器は27MHzで振動します。そのため、create_clockclkポートの周期を37.037ナノ秒(27MHz)に設定しています。

8.3.6 テスト

LEDの点灯を確認する

まず、LEDの点灯を確認します。

インポートされたtop_tang.svのtopモジュールをインスタンス化している場所で、MEMORY_FILEPATHパラメータの値をtest/led.hexのパスに設定します(リスト8.20)。

リスト8.20: リスト8.20: 読み込むファイルを設定する (top_tang.sv)
core_top #(
    .MEMORY_FILEPATH_IS_ENV (0 ),
    .MEMORY_FILEPATH        ("test/led.hexへのパス")
) t (

ProcessタブのSynthesizeをクリックし、合成します(図8.21)。

Processタブ

図8.21: Processタブ

そうすると、合成に失敗して図8.22のようなエラーが表示されます。

合成するとエラーが発生する

図8.22: 合成するとエラーが発生する

これは、Tang Nano 9Kに搭載されているメモリ用の部品の数が足りないために発生しているエラーです。この問題を回避するために、eeiパッケージのMEM_ADDR_WIDTHの値を10に変更します*3。メモリの幅を変更したら、Verylファイルをビルドしなおして、もう一度合成します。

[*3] 適当な値です

合成と配置配線に成功した

図8.23: 合成と配置配線に成功した

合成に成功したら、Place & Routeを押して、論理回路の配置配線情報を生成します(図8.23)。それが終了したら、Tang Nano 9KをPCに接続して、Gowin Programmerを開いて設計をFPGAに書き込みます(図8.24)。

Program / Configureボタンを押して書き込む

図8.24: Program / Configureボタンを押して書き込む

Tang Nano 9Kの中央2つ以外のLEDが点灯していることを確認できます。図8.5の左下のボタンを押すと全てのLEDが点灯します。

LEDの制御用レジスタの値が12(<code class="inline-code">64'b1100</code>)なので中央2つのLEDが点灯せず、それ以外が点灯する

図8.25: LEDの制御用レジスタの値が12(64'b1100)なので中央2つのLEDが点灯せず、それ以外が点灯する

MEMORY_FILEPATHパラメータの値をtest/led_counter.hexのパスに設定します(リスト8.21)。

リスト8.21: リスト8.21: 読み込むファイルを変更する (top_tang.sv)
core_top #(
    .MEMORY_FILEPATH_IS_ENV (0 ),
    .MEMORY_FILEPATH        ("test/led_counter.hexへのパス")
) t (

合成、配置配線しなおして、設計をFPGAに書き込むとLEDが点滅します*4図8.5の左下のボタンを押すと状態がリセットされます。

8.4 FPGAへの合成② (PYNQ-Z1)

PYNQ-Z1をターゲットにCPUを合成します。使用するEDAのバージョンは次の通りです。

  • Vivado v2023.2

初めてPYNQ-Z1を使う人は、PYNQのドキュメントやACRiの記事を参考に起動方法を確認して、Vivadoにボードファイルを追加してください。

8.4.1 合成用のモジュールを作成する

PYNQ-Z1のLED(6個)

図8.26: PYNQ-Z1のLED(6個)

PYNQ-Z1にはLEDが6個実装されています(図8.26)。本章ではボタンの上の横並びの4つのLED(図8.26右下)を使用します。

8.3.1 合成用のモジュールを作成する」とおなじように、ledポートのビット幅を一致させるためにPYNQ-Z1の合成のためのトップモジュールを作成します。

src/top_pynq_z1.verylを作成し、次のように記述します(リスト8.22)。top_pynq_z1モジュールのledポートは4ビットとして定義して、topモジュールのledポートの下位4ビットを接続しています。

リスト8.22: リスト8.22: PYNQ-Z1用の最上位モジュール (top_pynq_z1.veryl)
import eei::*;

module top_pynq_z1 #(
    param MEMORY_FILEPATH: string = "",
) (
    clk: input  clock   ,
    rst: input  reset   ,
    led: output logic<4>,
) {

    // CSRの下位ビットをLEDに接続する
    var led_top: UIntX;
    always_comb {
        led = led_top[3:0];
    }

    inst t: top #(
        MEMORY_FILEPATH_IS_ENV: 0              ,
        MEMORY_FILEPATH       : MEMORY_FILEPATH,
    ) (
        clk         ,
        rst         ,
        led: led_top,
        #[ifdef(TEST_MODE)]
        test_success: _,
    );
}

8.4.2 プロジェクトを作成する

Vivadoを開いて、プロジェクトを作成します。Quick StartのCreate Projectを押すと、図8.27が出るのでNextを押します。

Nextを押す

図8.27: Nextを押す

プロジェクト名とフォルダを入力します(図8.28)。好きな名前と場所を入力したらNextを押します。

プロジェクト名とフォルダを入力する

図8.28: プロジェクト名とフォルダを入力する

プロジェクトの形式を設定します(図8.29)。RTL Projectを選択して、Do not specify sources at this timeにチェックを入れてNextを押します。

プロジェクトの形式を選択する

図8.29: プロジェクトの形式を選択する

ターゲットのFPGAボードを選択します(図8.30)。今回はPYNQ-Z1がターゲットなので、Boardsタブに移動してPYNQ-Z1を選択します。PYNQ-Z1が表示されない場合、ボードファイルをVivadoに追加してください。

PYNQ-Z1を選択する

図8.30: PYNQ-Z1を選択する

概要を確認して、Nextを押します(図8.31)。

Nextを押す

図8.31: Nextを押す

プロジェクトが作成されました(図8.32)。

プロジェクトの画面

図8.32: プロジェクトの画面

8.4.3 設計ファイルを追加する

Verylのソースファイルをビルドして、生成されるファイルリスト(core.f)を利用して、生成されたSystemVerilogソースファイルをプロジェクトに追加します。

Vivadoでファイルを追加するには、ウィンドウ下部のTcl Console画面でadd_fileを実行します。しかし、add_fileはファイルリストの読み込みに対応していないので、ファイルリストを読み込んでadd_fileを実行するスクリプトを作成します(リスト8.23)。

リスト8.23: リスト8.23: add_files.tcl
set file_list [open "ファイルリストのパス" r]
while {[gets $file_list line] != -1} {
    # skip blank or comment line
    if {[string trim $line] eq "" || [string index $line 0] eq "#"} {
        continue
    }
    # add file to project
    add_files -force -norecurse $line
}
close $file_list

ウィンドウの下部にあるTcl Console画面で、次のコマンドを実行します(リスト8.24図8.33)。VerylをWSLで実行してVivadoをWindowsで開いている場合、ファイルリスト内のパスをWindowsから参照できるパスに変更する必要があります。

リスト8.24: リスト8.24: Tclスクリプトを実行する
source add_files.tclのパス
add_files.tclを実行する

図8.33: add_files.tclを実行する

ソースファイルが追加されました(図8.34)。

ソースファイルが追加された

図8.34: ソースファイルが追加された

8.4.4 Verilogのトップモジュールを作成する

VerylファイルはSystemVerilogファイルに変換されますが、VivadoではトップモジュールにSystemVerilogファイルを使用できません。この問題を回避するために、Verilogでtop_pynq_z1モジュールをインスタンス化するモジュールを記述します(リスト8.25)。

リスト8.25: リスト8.25: PYNQ-Z1用の最上位モジュール (core_top_v.v)
module core_top_v #(
    parameter MEMORY_FILEPATH = ""
) (
    input wire          clk,
    input wire          rst,
    output wire [3:0]   led
);
    core_top_pynq_z1 #(
        .MEMORY_FILEPATH(MEMORY_FILEPATH)
    ) t (
        .clk(clk),
        .rst(rst),
        .led(led)
    );
endmodule

core_top_v.vをadd_filesでプロジェクトに追加します(リスト8.26)。

リスト8.26: リスト8.26: Tcl Consoleで実行する
add_files -norecurse core_top_v.vのパス

8.4.5 ブロック図を作成する

Vivadoではモジュール間の接続をブロック図によって行えます。設計したモジュールをブロック図に追加して、クロックやリセット、LEDの接続を行います。

ブロック図の作成とトップモジュールの設定

画面左のFlow NavigatorでCreate Block Designを押してブロック図を作成します(図8.35)。

IP INTEGRATOR &gt; Create Block Design

図8.35: IP INTEGRATOR > Create Block Design

名前は適当なものに設定します(図8.36)。

名前を入力する

図8.36: 名前を入力する

Sourcesタブに作成されたブロック図が追加されるので、右クリックしてCreate HDL Wrapper...を押します(図8.37)。

Create HDL Wrapper...を押す

図8.37: Create HDL Wrapper...を押す

そのままOKを押します(図8.38)。

OKを押す

図8.38: OKを押す

ブロック図がVerilogモジュールになるので、Set as Topを押して、これをトップモジュールにします(図8.39)。

Set as Topを押す

図8.39: Set as Topを押す

ブロック図がトップモジュールに設定されました(図8.40)。

ブロック図がトップモジュールに設定された

図8.40: ブロック図がトップモジュールに設定された

ブロック図の設計

Diagram画面でブロック図を組み立てます。

まず、core_top_vモジュールを追加します。適当な場所で右クリックして、Add Module...を押します(図8.41)。

Add Module...を押す

図8.41: Add Module...を押す

core_top_vを選択して、OKを押します(図8.42)。

core_top_vを選択する

図8.42: core_top_vを選択する

core_top_vが追加されるので、ledポートをクリックしてMake Externalを押します(xilinx/bd/8)。

Make Externalを押す

図8.43: Make Externalを押す

led_0ポートが追加されました(図8.44)。これがブロック図のoutputポートになります。

led_0ポートが追加された

図8.44: led_0ポートが追加された

led_0を選択して、左側のExternal Port PropertiesのNameをledに変更します。

名前をledに変更した

図8.45: 名前をledに変更した

次に、+ボタンを押してZYNQ7 Processing System、Processor System Reset、Clocking Wizardを追加します(図8.46図8.47)。

+ボタンを押す

図8.46: +ボタンを押す

3つIPを追加する

図8.47: 3つIPを追加する

上にDesigner Assistance Availableと出るので、Run Block Automationを押します(図8.48)。

Run Block Automationを押す

図8.48: Run Block Automationを押す

図8.49のようになっていることを確認して、OKを押します。

DDRとFIXED_IOが追加された

図8.49: DDRとFIXED_IOが追加された

図8.50のようにポートを接続します。接続元から接続先にドラッグすることでポートを接続できます。また、proc_sys_reset_0のext_reset_inをMake Externalしてrstを作成してください。

ポートを接続する

図8.50: ポートを接続する

ZYNQ7 Processing Systemのoutputポートには、100MHzのクロック信号FCLK_CLK0が定義されています。これをそのままcore_top_vに供給しても良いですが、現状のコードではcore_top_vが100MHzで動くように合成できません。そのため、Clocking Wizardで50MHzに変換したクロックをcore_top_vに供給します。

clk_wiz_0をダブルクリックして、入力を50MHzに変換する用に設定します。clk_out1のRequestedを50に変更してください。また、Enable Optional ~のresetとlockedのチェックを外します(図8.51)。

Clocking Wizardの設定を変更する

図8.51: Clocking Wizardの設定を変更する

clk_wiz_0が少しコンパクトになりました(図8.52)。

Clocking Wizardが変更された

図8.52: Clocking Wizardが変更された

8.4.6 制約ファイルを作成する

ブロック図のrst、ledを、それぞれPYNQ-Z1のボタン(BTN0)、LED(LD0、LD1、LD2、LD3)に接続します。接続の設定には物理制約ファイルを作成します。

pynq.xdcを作成し、次のように記述します(リスト8.27)。

リスト8.27: リスト8.27: 物理制約ファイル (pynq.xdc)
# reset (BTN0)
set_property -dict { PACKAGE_PIN D19 IOSTANDARD LVCMOS33} [ get_ports rst ]

# led (LD0 - LD4)
set_property -dict { PACKAGE_PIN R14 IOSTANDARD LVCMOS33 } [ get_ports led[0] ];
set_property -dict { PACKAGE_PIN P14 IOSTANDARD LVCMOS33 } [ get_ports led[1] ];
set_property -dict { PACKAGE_PIN N16 IOSTANDARD LVCMOS33 } [ get_ports led[2] ];
set_property -dict { PACKAGE_PIN M14 IOSTANDARD LVCMOS33 } [ get_ports led[3] ];

リスト8.27では、rstにD19を割り当てて、led[0]、led[1]、led[2]、led[3]にR14、P14、N16、M14を割り当てます(図8.53)。ボタンは押されていないときに1、押されているときに0になります。LEDは1のときに点灯して、0のときに消灯します。

LEDとボタン<a href="bib.html#bib-pynq_z1.manual">[13]</a>

図8.53: LEDとボタン[13]

8.4.7 テスト

LEDの点灯を確認する

ブロック図のcore_top_v_0をダブルクリックすることで、core_top_vモジュールのMEMORY_FILEPATHパラメータを変更します。パラメータにはテストのHEXファイルのパスを設定します(図8.54)。LEDの点灯のテストのためにtest/led.hexのパスを入力します。

テストのHEXファイルのパスを設定する

図8.54: テストのHEXファイルのパスを設定する

PROGRAM AND DEBUGのGenerate Bitstreamを押して合成と配置配線を実行します(図8.55)。

合成、配置配線

図8.55: 合成、配置配線

合成が完了したらOpen Hardware Managerを押して、開かれたHARDWARE MANAGERのOpen TargetAuto Connectを押してPYNQ-Z1と接続します(図8.56)。

PYNQ-Z1を接続する

図8.56: PYNQ-Z1を接続する

Program deviceを押すと、PYNQ-Z1に設計が書き込まれます。

設計を書き込む

図8.57: 設計を書き込む

LEDが点灯しているのを確認できます(図8.58)。BTN0を押すとLEDが消灯します。

LEDの制御用レジスタの値が12(<code class="inline-code">64'b1100</code>)なので、LD3、LD2が点灯する

図8.58: LEDの制御用レジスタの値が12(64'b1100)なので、LD3、LD2が点灯する

core_top_vモジュールのMEMORY_FILEPATHパラメータの値をtest/ledcounter.hexのパスに変更して、再度Generate Bitstreamを実行します。

Hardware Managerを開いてProgram deviceを押すとLEDが点滅します*5。BTN0を押すと状態がリセットされます。