自作RTLにAXI4-Liteインタフェース(M側)を追加する方法 (3) “Hello World”をシリアル出力するFPGAロジックの作成

AXI4
スポンサーリンク

本記事の概要

概要

本記事では、AXI4-LiteインタフェースをもつカスタムIPとXilinx社が提供するIP”AXI UART Lite”とを組み合わせて、”Hello World”文字列をシリアル出力させました。
ロジック回路をProgrammable Logic (PL)に作成し、Zybo上で実装・動作確認を行いました。

前回の記事では、Xilinx社が提供するVivadoのIPパッケージャーという機能を用いて、自作のカスタムIPにAXIプロトコルの入出力インターフェースを追加する方法について解説し、シリアル出力回路を例にXilinx社のIP”AXI UART Lite”のAXI4-Liteポートと接続可能なカスタムIPを作成しました。

今回の記事では、このカスタムIPに順次”Hello World”という文字列を伝送するためのIPを紹介した後、実際にZybo上で動作確認をした結果をまとめます。

本記事の対象読者:以下の状況に直面している方
  • 自作RTLモジュールの接続先IPにAXI4インタフェースしか用意されていないような場合
  • カスタムIPに制御レジスタやステータスレジスタを設け、プロセッサからそれらを参照したい場合

目標

本記事全体の目標は次の通りです:

自作のRTLモジュールにAXI4-Liteインタフェース(M側)を追加する

今回は、カスタムIPからAXI4-Liteを通じて、XilinxのIP”AXI UART Lite”のデータレジスタに値を書き込み、文字列“Hello world!!”を外部PCに出力させてみました。

Vivadoブロックデザインの作成

全体構成

ブロックデザインは以下のようにしました。

このブロックデザインの動作原理は以下のとおりです。

Hello Worldシリアル出力ロジックの動作原理
  1. Zyboのボタンを押し、BTNがHigh状態にアサートされる
  2. RTLモジュール”debounce.v”でボタン入力”BTN”のチャタリング除去を行う
  3. チャタリング除去後の”BTN”をトリガーにRTLモジュール”word_send.v”をアサートする。”Hello World!!”の文字列を、1バイトずつ順次カスタムIP”UART_OUT”に送信する
  4. “UART_OUT”は受信したデータ信号を1バイトずつIP”AXI UART Lite”のTx FIFOレジスタに書き込む
  5. IP”AXI UART Lite”はTx FIFOレジスタ内のデータを順次シリアル信号に変換し、UART_TXD_INポートを通じて、外部に出力する

チャタリング除去に用いたRTLモジュール”debounce.v”は、「FPGAプログラミング大全」に記載されているコードをそのまま用いています。

以下では、私が自作した”word_send.v”のソースコードについて解説します。

RTLモジュール”word_send.v”

ソースコード

`timescale 1ns / 1ps

module word_send(
     CLK
    ,RSTN
    ,btn_in

    ,AXI_DATA_en  
    ,AXI_DATA
    );

    localparam          PERIOD = 10000; //100us
    localparam          WORDNUM = 13;
//------------------------------------------------
    input               CLK;
    input               RSTN;
    input               btn_in;
    
    output              AXI_DATA_en;
    output [7:0]        AXI_DATA;
//------------------------------------------------

//-----------------------------------------------------------------------
// Generation of counter
//-----------------------------------------------------------------------
    reg     [23:0]          count       = 24'b0;
    reg     [3:0]           wordcount   = 4'b0;
    reg                     load        = 1'b0;
    wire                    countend;
    wire                    wordcountend;
    
    always @( posedge CLK ) begin
        if ( btn_in )           load <= 1'b1;
        else if (wordcountend)  load <= 1'b0;
    end
    always @( posedge CLK ) begin
        if (countend)           count <= 24'h000;
        else if (load)          count <= count + 24'h001;
    end
    assign countend     = (count == PERIOD - 24'b1);
    always @( posedge CLK ) begin
        if (wordcountend)       wordcount <= 4'b0;
        else if (countend)      wordcount <= wordcount + 4'b1;
    end
    assign wordcountend = (wordcount == WORDNUM) && countend;

//-----------------------------------------------------------------------
// WORD send
//-----------------------------------------------------------------------
    assign  AXI_DATA_en = (count == 24'h001);
    assign  AXI_DATA    = hello_sentence(wordcount);

    function [7:0]  hello_sentence;
        input   [3:0]       wordcount;
        begin
            case (wordcount)
                4'd0    : hello_sentence = 8'h48; //H
                4'd1    : hello_sentence = 8'h65; //e
                4'd2    : hello_sentence = 8'h6c; //l
                4'd3    : hello_sentence = 8'h6c; //l
                4'd4    : hello_sentence = 8'h6f; //o
                4'd5    : hello_sentence = 8'h20; //space
                4'd6    : hello_sentence = 8'h57; //W
                4'd7    : hello_sentence = 8'h6f; //o
                4'd8    : hello_sentence = 8'h72; //r
                4'd9    : hello_sentence = 8'h6c; //l
                4'd10   : hello_sentence = 8'h64; //d
                4'd11   : hello_sentence = 8'h21; //!
                4'd12   : hello_sentence = 8'h21; //!
                4'd13   : hello_sentence = 8'h0a; //LF, new line 
                default : hello_sentence = 8'h00;
            endcase
        end
    endfunction

endmodule

タイミングチャート

ソースコードの説明を、word_send.vのタイミングチャートを使いながら説明します。

チャタリング除去後のボタン入力btn_inをトリガーにカウンターが回り始めます。
カウンターは(100マイクロ秒/バイト)×(送信するバイト数)だけ回ります。その間、load変数はHigh状態にアサートされています。

例えば、”Hello World!!(+改行)”は全部で13バイトなので1.3ミリ秒になります。

1バイト送信する100マイクロ秒の間に、AXI_DATA_ENがHigh状態にアサートされ、前回の記事で作成したカスタムIP”UART_OUT”に1文字分のデータを送信します。
ボーレートが115200bpsのとき、シリアル通信で1バイトの文字を送るのにかかる時間がおよそ87マイクロ秒なので、1語の送信間隔を100マイクロ秒だけ開ければ、余裕を持って送信することが可能です。

hello_sentenceという関数は、アスキーコード表を参考にして作成しています。

実装と動作確認

実機構成

ZyboとPCの接続は図のようにしています。
ZyboのPmodからシリアル信号を出力し、シリアルアダプターを用いてPCに読み込みました。

シリアルアダプターは、DSD TECH社のFTDI TTLアダプターを用いています。
値段も手頃で、3.3/5.0Vの両方に対応しているので使い勝手が良いと思います。ケースに入っているのも安心感がありますね。

ZyboのPmodはPmod JE(Standard Pmod)を選択しています。

Zybo Reference Manual - Digilent Reference
Zybo Reference Manual Note The Zybo Zynq-7000 has been retired and replaced by the Zybo Z7. If you need assistance with migration to the Zybo Z7, please follo...

下のZyboの写真におけるY17をRXDT17をTXDにしました。

https://reference.digilentinc.com/_media/zybo/zybopins.png?cache=より抜粋

それぞれのPmodピンとシリアルアダプターのピンとをジャンパ線を使って接続しています。
接続の対応関係は以下の通りです。

ジャンパ線の接続(Pmod↔シリアルアダプター)
  • JE12 PWR ↔ VCC
  • JE11 GND ↔ GND
  • JE10 Y17  ↔ TXD
  • JE09 T17  ↔ RXD

制約ファイルにおけるピン配置

この接続を踏まえて、制約ファイルを作成しました。

##Clock signal
##IO_L11P_T1_SRCC_35    
set_property PACKAGE_PIN L16 [get_ports sys_clock]
set_property IOSTANDARD LVCMOS33 [get_ports sys_clock]
create_clock -add -name sys_clk_pin -period 8.00 -waveform {0 4} [get_ports sys_clock]

##Buttons
##IO_L20N_T3_34
set_property PACKAGE_PIN R18 [get_ports {BTN}]
set_property IOSTANDARD LVCMOS33 [get_ports {BTN}]

##IO_L24N_T3_34
set_property PACKAGE_PIN P16 [get_ports {RST}]
set_property IOSTANDARD LVCMOS33 [get_ports {RST}]

##Pmod
#IO_L20P_T3_34
set_property PACKAGE_PIN T17 [get_ports {UART_TXD_IN}]
set_property IOSTANDARD LVCMOS33 [get_ports {UART_TXD_IN}]

#IO_L7N_T1_34
set_property PACKAGE_PIN Y17 [get_ports {UART_RXD_OUT}]
set_property IOSTANDARD LVCMOS33 [get_ports {UART_RXD_OUT}]

Pmodの接続に加えて、システムクロックとリセットポート、そしてトリガーをかけるためのボタンの接続ピンの配置を設定しました。

制約ファイルができましたら、[Generate Bitstream]で論理合成、配置配線、bitstreamファイルの作成までを一気に行います。

ZyboをPCに接続し、作成したbitファイルの書き込みまでを行い、シリアルアダプターをPCに接続しました。

Tera Termの設定

UART信号を確認するためのターミナル画面にTera Termを用いました。

Tera Termを立ち上げたら、シリアルを選択し、シリアルアダプターを接続しているポートを選択します。

ターミナル画面が立ち上がったら、[設定]>[端末]を選択します。

ここでは、改行コードの受信をLF、送信をCR+LF、そしてローカルエコーにチェックを入れます。
選択が終わったら、[OK]をクリックしましょう

次に、[設定]>[シリアルポート]を選択します。

スピードを、IP”AXI UART Lite”のボーレートと一致させます。今回は、115200です。
終わったら、[現在の接続を再設定]を選択します。

以上で、Tera Termの設定は完了です。

”Hello World!”出力確認

では、ボタン(BTN0)を押してみましょう

”Hello World!!”が無事に出力されました!

ひがし
ひがし

前々回、前回の記事から、M側のAXI4-Liteインタフェースを持つカスタムIPの作成を行い、今回の記事で実機での動作確認までを行うことができました。
プロセッサを使わずにAXI4-Lite経由で自由に書き込みや読み出しができるようになると、IP活用の幅が広がると思います。

最後までご覧いただきありがとうございました!

スポンサーリンク


参考:開発環境

環境
  • 開発用PC: Windows 10, 64bit
    • Vivado Design Suite – HLx Edition – 2020.2
    • Vitis コア開発キット – 2020.2
  • 開発用基板: Zybo Zynq-7010評価ボード(Board Rev.4)
    • Zynq XC7Z010-1CLG400C

コメント