Skip to content

CPUの合成

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

PYNQ-Z1

▲図1: PYNQ-Z1

FPGAとは何か?

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

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

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

表8.1: 真理値表の例

XYA
000
011
101
110
![表1を実現するLUT](./images/05b-synth/lut.drawio.png)

▲図2: 表1を実現するLUT

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

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

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

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

LEDの制御

Tang Mega 138K ProのLED(6個)

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

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

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

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

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

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

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

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

▼リスト8.1: LEDの制御用レジスタのアドレスを追加する (csrunit.veryl) 差分をみる

veryl
enum CsrAddr: logic<12> {
    MTVEC = 12'h305,
    MEPC = 12'h341,
    MCAUSE = 12'h342,
    LED = 12'h800,
}

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

▼リスト8.2: LEDの制御用レジスタの書き込みマスク (csrunit.veryl) 差分をみる

veryl
const LED_WMASK   : UIntX = 'hffff_ffff_ffff_ffff;

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

▼リスト8.3: LEDの制御用レジスタを定義する (csrunit.veryl) 差分をみる

veryl
module csrunit (
    clk        : input  clock       ,
    rst        : input  reset       ,
    valid      : input  logic       ,
    pc         : input  Addr        ,
    ctrl       : input  InstCtrl    ,
    rd_addr    : input  logic   <5> ,
    csr_addr   : input  logic   <12>,
    rs1        : input  UIntX       ,
    rdata      : output UIntX       ,
    raise_trap : output logic       ,
    trap_vector: output Addr        ,
    led        : output UIntX       ,
) {

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

▼リスト8.4: rdataとwmaskに値を割り当てる (csrunit.veryl) 差分をみる

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に設定します(リスト5)。

▼リスト8.5: リセット値の設定 (csrunit.veryl) 差分をみる

veryl
if_reset {
    mtvec  = 0;
    mepc   = 0;
    mcause = 0;
    led    = 0;
} else {

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

▼リスト8.6: LEDの制御用レジスタへの書き込み (csrunit.veryl) 差分をみる

veryl
case csr_addr {
    CsrAddr::MTVEC : mtvec  = wdata;
    CsrAddr::MEPC  : mepc   = wdata;
    CsrAddr::MCAUSE: mcause = wdata;
    CsrAddr::LED   : led    = wdata;
    default        : {}
}

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

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

▼リスト8.7: coreモジュールにポートを追加する (core.veryl) 差分をみる

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: csrunitモジュールのledポートと接続する (core.veryl) 差分をみる

veryl
inst csru: csrunit (
    clk                            ,
    rst                            ,
    valid   : mems_valid           ,
    pc      : mems_pc              ,
    ctrl    : mems_ctrl            ,
    rd_addr : mems_rd_addr         ,
    csr_addr: mems_inst_bits[31:20],
    rs1     : if mems_ctrl.funct3[2] == 1 && mems_ctrl.funct3[1:0] != 0 ?
        {1'b0 repeat XLEN - $bits(memq_rdata.rs1_addr), memq_rdata.rs1_addr} // rs1を0で拡張する
    :
        memq_rdata.rs1_data
    ,
    rdata      : csru_rdata      ,
    raise_trap : csru_raise_trap ,
    trap_vector: csru_trap_vector,
    led                          ,
);

▼リスト8.9: topモジュールにポートを追加する (top.veryl) 差分をみる

veryl
module top #(
    param MEMORY_FILEPATH_IS_ENV: bit    = 1                 ,
    param MEMORY_FILEPATH       : string = "MEMORY_FILE_PATH",
) (
    #[ifdef(TEST_MODE)]
    test_success: output bit  ,
    clk         : input  clock,
    rst         : input  reset,
    led         : output UIntX,
) {

▼リスト8.10: coreモジュールのledポートと接続する (top.veryl) 差分をみる

veryl
inst c: core (
    clk       ,
    rst       ,
    i_membus  ,
    d_membus  ,
    led       ,
);

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

テストを作成する

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

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

▼リスト8.11: LEDを点灯させるプログラム (test/led.asm) 差分をみる

asm
80065073 //  0: csrrwi x0, 0x800, 12
00000067 //  4: jal x0, 0

▼リスト8.12: LEDを点灯させるプログラム (test/led.hex) 差分をみる

hex
0000006780065073

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

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

▼リスト8.13: LEDを点滅させるプログラム (test/led_counter.asm) 差分をみる

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: LEDを点滅させるプログラム (test/led_counter.hex) 差分をみる

hex
24008093000f40b7
0011011300000113
800031f3fe209ee3
8001907300118193
0000006700000067

リスト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と値が変わっていきます。

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

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

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

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

Tang Nano 9KのLED(6個)

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

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

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

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

▼リスト8.15: Tang Nano 9K用の最上位モジュール (top_tang.veryl) 差分をみる

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       : "",
    ) (
        #[ifdef(TEST_MODE)]
        test_success: _,

        clk         ,
        rst         ,
        led: led_top,
    );
}

プロジェクトを作成する

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

FPGA Design Projectを選択する

▲図6: FPGA Design Projectを選択する

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

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

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

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

ターゲットを選択する

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

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

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

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

設定を変更する

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

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

設定画面を開く

▲図10: 設定画面を開く

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

設定を変更する

▲図11: 設定を変更する

設計ファイルを追加する

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

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

▼リスト8.16: add_files.tcl 差分をみる

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画面で、次のコマンドを実行します(リスト17、図12)。 VerylをWSLで実行してGOWIN FPGA DesignerをWindowsで開いている場合、ファイルリスト内のパスをWindowsから参照できるパスに変更する必要があります。

▼リスト8.17: Tclスクリプトを実行する

add_files
source add_files.tclのパス

コマンドを実行する

▲図12: コマンドを実行する

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

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

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

制約ファイルを作成する

物理制約

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

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

New Fileを選択する

▲図14: New Fileを選択する

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

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

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

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

名前を設定する

▲図16: 名前を設定する

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

▼リスト8.18: 物理制約ファイル (tangnano9k.cst) 差分をみる

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のデータシートで確認できます。 例えば図17と図18から、 LEDは101113141516に割り当てられていることが分かります。 また、LEDが負論理(1で消灯、0で点灯)であることが分かります。 水晶発信器とボタンについても、データシートを見て確認してください。

LED

▲図17: LED

PIN10_IOL~の接続先

▲図18: PIN10_IOL~の接続先

タイミング制約

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

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

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

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

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

名前を設定する

▲図20: 名前を設定する

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

▼リスト8.19: タイミング制約ファイル (timing.sdc) 差分をみる

sdc
create_clock -name clk -period 37.037 -waveform {0 18.518} [get_ports {clk}]

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

テスト

LEDの点灯を確認する

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

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

▼リスト8.20: 読み込むファイルを設定する (top_tang.sv)

hex
core_top #(
    .MEMORY_FILEPATH_IS_ENV (0 ),
    .MEMORY_FILEPATH        ("test/led.hexへのパス")
) t (

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

Processタブ

▲図21: Processタブ

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

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

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

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

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

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

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

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

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

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

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

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

LEDの点滅を確認する

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

▼リスト8.21: 読み込むファイルを変更する (top_tang.sv)

hex
core_top #(
    .MEMORY_FILEPATH_IS_ENV (0 ),
    .MEMORY_FILEPATH        ("test/led_counter.hexへのパス")
) t (

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

FPGAへの合成② (PYNQ-Z1)

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

  • Vivado v2023.2

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

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

PYNQ-Z1のLED(6個)

▲図26: PYNQ-Z1のLED(6個)

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

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

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

▼リスト8.22: PYNQ-Z1用の最上位モジュール (top_pynq_z1.veryl) 差分をみる

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,
    ) (
        #[ifdef(TEST_MODE)]
        test_success: _,

        clk         ,
        rst         ,
        led: led_top,
    );
}

プロジェクトを作成する

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

Nextを押す

▲図27: Nextを押す

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

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

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

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

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

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

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

PYNQ-Z1を選択する

▲図30: PYNQ-Z1を選択する

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

Nextを押す

▲図31: Nextを押す

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

プロジェクトの画面

▲図32: プロジェクトの画面

設計ファイルを追加する

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

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

▼リスト8.23: add_files.tcl 差分をみる

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画面で、次のコマンドを実行します(リスト24、図33)。 VerylをWSLで実行してVivadoをWindowsで開いている場合、ファイルリスト内のパスをWindowsから参照できるパスに変更する必要があります。

▼リスト8.24: Tclスクリプトを実行する

add_files
source add_files.tclのパス

add_files.tclを実行する

▲図33: add_files.tclを実行する

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

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

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

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

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

▼リスト8.25: PYNQ-Z1用の最上位モジュール (core_top_v.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でプロジェクトに追加します(リスト26)。

▼リスト8.26: Tcl Consoleで実行する

add_files
add_files -norecurse core_top_v.vのパス

ブロック図を作成する

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

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

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

IP INTEGRATOR  Create Block Design

▲図35: IP INTEGRATOR > Create Block Design

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

名前を入力する

▲図36: 名前を入力する

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

Create HDL Wrapper...を押す

▲図37: Create HDL Wrapper...を押す

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

OKを押す

▲図38: OKを押す

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

Set as Topを押す

▲図39: Set as Topを押す

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

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

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

ブロック図の設計

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

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

Add Module...を押す

▲図41: Add Module...を押す

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

core_top_vを選択する

▲図42: core_top_vを選択する

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

Make Externalを押す

▲図43: Make Externalを押す

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

led_0ポートが追加された

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

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

名前をledに変更した

▲図45: 名前をledに変更した

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

+ボタンを押す

▲図46: +ボタンを押す

3つIPを追加する

▲図47: 3つIPを追加する

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

Run Block Automationを押す

▲図48: Run Block Automationを押す

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

DDRとFIXED_IOが追加された

▲図49: DDRとFIXED_IOが追加された

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

ポートを接続する

▲図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のチェックを外します(図51)。

Clocking Wizardの設定を変更する

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

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

Clocking Wizardが変更された

▲図52: Clocking Wizardが変更された

制約ファイルを作成する

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

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

▼リスト8.27: 物理制約ファイル (pynq.xdc) 差分をみる

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] ];

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

LEDとボタン[^26]

▲図53: LEDとボタン[^27]

テスト

LEDの点灯を確認する

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

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

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

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

合成、配置配線

▲図55: 合成、配置配線

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

PYNQ-Z1を接続する

▲図56: PYNQ-Z1を接続する

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

設計を書き込む

▲図57: 設計を書き込む

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

LEDの制御用レジスタの値が12()なので、LD3、LD2が点灯する

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

LEDの点滅を確認する

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

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


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

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

  3. 適当な値です ↩︎

  4. https://youtu.be/OpXiXha-ZnI ↩︎

  5. https://youtu.be/byCr_464dW4 ↩︎