FC2カウンター FPGAの部屋 ZYBO Z7-20 の Debian Linux で gettimeofday() と axi_timer の値を比べる
fc2ブログ

FPGAやCPLDの話題やFPGA用のツールの話題などです。 マニアックです。 日記も書きます。

FPGAの部屋

FPGAの部屋の有用と思われるコンテンツのまとめサイトを作りました。Xilinx ISEの初心者の方には、FPGAリテラシーおよびチュートリアルのページをお勧めいたします。

ZYBO Z7-20 の Debian Linux で gettimeofday() と axi_timer の値を比べる

ZYBO Z7 の Debian LInux からパソコンへ TCP/IP で 3.333 ms 毎にデータを送る”で、ZYBO Z7-20 の Debian Linux の gettimeofday() の精度はどのくらいなのだろう?ということで、axi_timer_0 の Timer2 をイネーブルして、axi timer の値と比較してみた。

ikwzm さんの Debian Linux 上で axi timer の割り込みを試す”で作成した Vivado 2022.2 プロジェクトの timer_test の timer_test ブロック・デザインの axi_timer_0 をダブルクリックして、Enable Timer2 のチェックボックスにチェックを入れた。
TCPsoft_11_230219.png

セーブして、論理合成、インプリメンテーション、ビットストリームの生成を行った。
Project Summary を示す。
TCPsoft_12_230219.png

timer_test/timer_test.runs/impl_1/timer_test_wrapper.bit が再生成されていた。
timer_test_wrapper.bit を ZYBO Z7-20 上に SFTP で転送した。
fpga-bit-to-bin.py も転送して bit ファイルを bin ファイルに転送した。
python3 fpga-bit-to-bin.py --flip timer_test_wrapper.bit timer_test.bin

timer_test.bin を /lib/firmware ディレクトリにコピーする。
sudo cp timer_test.bin /lib/firmware

device_tree.dts は”ikwzm さんの Debian Linux 上で axi timer の割り込みを試す”のdevice_tree.dts がそのまま使用できる。

dtgocfg.rb でデバイスツリーやビットストリームをロードする。
sudo dtbocfg.rb -i --dts devicetree.dts timer test

uio の権限を 666 に変更してユーザーもオープンできるようにする。
sudo chmod 666 /dev/uio*

tcp_timer_test.c に Timer2 の記述を追加した。

// tcp_timer_test.c
// 2023/02/14 by marsee
// Reference URL:https://github.com/ikwzm/ZYBO_UIO_IRQ_SAMPLE/blob/master/c-sample/sample1.c
// 何回もクライアントと接続できるサーバ例(エラー処理付)のコードを引用した。
// https://www.geekpage.jp/programming/linux-network/tcp-3.php
//
// 2023/02/17 : axi timerのTimer2の記述を追加した

#include        <stdio.h>
#include        <stdint.h>
#include        <stdlib.h>
#include        <fcntl.h>
#include        <string.h>
#include        <time.h>
#include        <sys/time.h>
#include        <poll.h>
#include        <sys/types.h>
#include        <sys/mman.h>
#include        <sys/socket.h>
#include        <netinet/in.h>
#include        <string.h>


#define ENABLE_ALL_TIMERS               (0x1<<10)
#define ENABLE_PULSE_WIDTH_MODULATION   (0x1<<9)
#define TIMER_INTERRUPT                 (0x1<<8)
#define ENABLE_TIMER                    (0x1<<7)
#define ENABLE_INTERRUPT                (0x1<<6)
#define LOAD_TIMER                      (0x1<<5)
#define AUTO_RELOAD_HOLD_TIMER          (0x1<<4)
#define ENABLE_EXT_CAPTURE_TRIG         (0x1<<3)
#define ENABLE_EXT_GENERATE_SIG         (0x1<<2)
#define DOWN_UP_COUNT_TIMER             (0x1<<1)
#define TIMER_MODE_CAP_GENE             (0x1)

int uio_irq_on(int uio_fd)
{
    unsigned int  irq_on = 1;
    write(uio_fd, &irq_on, sizeof(irq_on));
}

int uio_wait_irq(int uio_fd)
{
    unsigned int  count = 0;
    return read(uio_fd, &count,  sizeof(count));
}

void main()
{
    int            uio0_fd, uio1_fd;
    uint32_t    *axi_gpio, *axi_timer;
    static uint32_t led_stat = 0;
    int sock0;
    struct sockaddr_in addr;
    struct sockaddr_in client;
    int len;
    int sock;
    int n;
    struct tm *time_st;
    struct timeval myTime;
    char buf[500];
    uint32_t timer_data;


    if((uio0_fd = open("/dev/uio0", O_RDWR)) == -1) {
        printf("Can not open /dev/uio0\n");
        exit(1);
    }
    axi_gpio = (uint32_t*)mmap(NULL, 0x10000, PROT_READ|PROT_WRITE, MAP_SHARED, uio0_fd, 0);
    
    if((uio1_fd = open("/dev/uio1", O_RDWR)) == -1) {
        printf("Can not open /dev/uio1\n");
        exit(1);
    }
    axi_timer = (uint32_t*)mmap(NULL, 0x10000, PROT_READ|PROT_WRITE, MAP_SHARED, uio1_fd, 0);
    
    // axi_timer_0 -> Timer0
    axi_timer[1] = 333333; // TLR0, 100 MHz = 3.33333 ms
    axi_timer[0] = LOAD_TIMER; // TCR0, ENALL and LOAD0 = 1
    axi_timer[0] = TIMER_INTERRUPT | ENABLE_TIMER | ENABLE_INTERRUPT | AUTO_RELOAD_HOLD_TIMER | DOWN_UP_COUNT_TIMER; // timer interrupt clear
    axi_timer[5] = 0; // TLR1
    axi_timer[4] = ENABLE_TIMER | AUTO_RELOAD_HOLD_TIMER; // UP Counter
    
    // network settings
    sock0 = socket(AF_INET, SOCK_STREAM, 0);
    if (sock0 < 0) {
     perror("socket");
     return 1;
    }

    addr.sin_family = AF_INET;
    addr.sin_port = htons(12345);
    addr.sin_addr.s_addr = INADDR_ANY;

    if (bind(sock0, (struct sockaddr *)&addr, sizeof(addr)) != 0) {
     perror("bind");
     return 1;
    }

    if (listen(sock0, 5) != 0) {
     perror("listen");
     return 1;
    }
    
    len = sizeof(client);
    sock = accept(sock0, (struct sockaddr *)&client, &len);
    if (sock < 0) {
        perror("accept");
    }
    for(int i=0; i<10000; i++){
        if(uio_irq_on(uio1_fd) == -1){
            fprintf(stderr, "uio_irq_on error\n");
            break;
        }
        if(uio_wait_irq(uio1_fd) == -1){
            fprintf(stderr, "uio_wait_irq error\n");
            break;
        }
        
        axi_timer[0] = TIMER_INTERRUPT | ENABLE_TIMER | ENABLE_INTERRUPT | AUTO_RELOAD_HOLD_TIMER | DOWN_UP_COUNT_TIMER; // timer interrupt clear
        
        gettimeofday(&myTime, NULL);
        uint32_t count = axi_timer[6];
        sprintf(buf, "36066078,140059917,54448,1,19,-51,0,1,-36,-57,0,1,%07.3f,%010u,ffdee,fd2fe,f526,fe4df,ffa01,f7a8,ffe09,ffcd8,f8d6,ffee1,ffdd6,f8ec,e6b,ff0eb,f8e3,fee1a,1e19,f8ac,ff3a4,fcf05,f464,bd3,ffe69,f8d4,967,ff841,f7e2,9658,178f,f4c8,0,a\n", (float)myTime.tv_usec/1000.0, count);
        n = write(sock, buf, strlen(buf));
        if (n < 1) {
            perror("write");
            break;
        }
        usleep(1000); // wait 1 ms
    }
    close(uio0_fd);
    close(uio1_fd);
    close(sock);
    close(sock0);
}


ZYBO Z7-20 上の Debian Linux で tcp_timer_test.c をコンパイルした。
gcc -o tcp_timer_test tcp_timer_test.c

データ転送量が増えたので、tcp_client.c は tcp_client2.c として変更した。

// tcp_client.c
// 2023/02/02 by marsee
// TCPを使うの単純なTCPクライアントを引用した。
// https://www.geekpage.jp/programming/linux-network/tcp-1.php
// 2023/02/17 : 受信バイト数を234バイトに変更

#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>

int
main()
{
     struct sockaddr_in server;
     int sock;
     char buf[500];
     int n;

     /* ソケットの作成 */
     sock = socket(AF_INET, SOCK_STREAM, 0);

     /* 接続先指定用構造体の準備 */
     server.sin_family = AF_INET;
     server.sin_port = htons(12345);
     server.sin_addr.s_addr = inet_addr("192.168.11.12");

     /* サーバに接続 */
     connect(sock, (struct sockaddr *)&server, sizeof(server));

     for(int i=0; i<10000; i++){
         /* サーバからデータを受信 */
         memset(buf, 0, 234);
         n = read(sock, buf, 234);

         printf("%s", buf);
     }

     /* socketの終了 */
     close(sock);

 return 0;
}


tcp_client2.c をコンパイルした。
gcc -o tcp_client2 tcp_client2.c

ZYBO Z7-20 上の Debian Linux でサーバー側ソフトウェアの tcp_timer_test を実行する。
./tcp_timer_test

パソコンの Windows 10 の WSL2 でクライアント側のソフトウェアを実行する。その際に temp.txt に転送されたデータをセーブした。
./tcp_client2 > temp.txt

1 万個のデータが送られてきたので、Excel で集計を行った。
TCPsoft_8_230217.png

AU 列が gettimeofday() での時刻の差分、AV 列が axi timer でのカウント値を ms オーダーに直した値の差分、AW 列が AU 列と AV 列の差分の絶対値を示す(それぞれ、単位は ms)。
AW 列に示す gettimeofday() の差分と axi timer の差分の差分の絶対値は小さいと見ることができると思う。最大で 1.32 us となっている。gettimeofday() は正確のようだ。

また、もう 1 万個のデータをとってみて Excel で集計してみた(それぞれ、単位は ms)。
TCPsoft_9_230217.png

今度は、AW 列に示す gettimeofday() の差分と axi timer の差分の差分の絶対値も増大していたし、AU 列が gettimeofday() での時刻の差分、AV 列が axi timer でのカウント値を ms オーダーに直した値の差分も最大値と最小値の差が大きくなっていた。
どこで、最大値と最小値の差が大きくなったか調べると、7873 番目と 7874 番目の時刻が最大値と最小値になっている。
TCPsoft_10_230217.png

7873 番目に CPU の負荷が重くて遅れてしまったので、7874 番目の割り込みの時刻の間隔が短くなったのだろう?
これが許容できない場合は、Debian Linux で割り込みでデータを取得するのではなく、他の方法を探ることにする。
  1. 2023年02月19日 05:39 |
  2. FPGAを使用したシステム
  3. | トラックバック:0
  4. | コメント:0

コメント

コメントの投稿


管理者にだけ表示を許可する

トラックバック URL
https://marsee101.blog.fc2.com/tb.php/5857-381a2e4c
この記事にトラックバックする(FC2ブログユーザー)