ZYBOで遊ぶ02:AXI CDMA IPを使ってみた(2)

ZYBOで遊んだメモ

ZYBOで遊ぶ02:AXI CDMA IPを使ってみた(1) - ThuruThuruToru’s blog の続き


ZYBOで遊ぶ02:AXI CDMA IPを使ってみた(1) - ThuruThuruToru’s blog
ZYBOで遊ぶ02:AXI CDMA IPを使ってみた(2) - ThuruThuruToru’s blog



2. AXI CDMAの最低限の仕様を確認

Xilinx SDKでSWを書き始める前に,AXI CDMAの動かし方を調べる.

XilinxのWebページ XilinxのAXI CDMA のPG034 - AXI Central Direct Memory Access v4.1 Product Guide に仕様書が置かれている.

Register Details, Programming Sequence の節を読めば動かし方がなんとなくわかる.

2.1. 制御レジスタ

今回はScatter/Gatherはなしなので,以下の制御レジスタを使うだけで動かせる.

  • CDMACR
    • Err_IrqEn
      • 1にセットでエラー時の割り込みを有効化
    • IOC_IrqEn
      • 1にセットで転送完了割り込みを有効化
  • CDMASR
    • Err_Irq
      • エラー発生時に1がセットされる
      • 1をWriteするとクリア
    • IOC_Irq
      • 転送完了時に1がセットされる
      • 1をWriteするとクリア
    • DMADecErr, DMASlvErr, DMAIntErr
      • エラー情報が読める
    • Idle
      • Idle状態なら1が読める
  • SA
    • Sourceアドレスを指定する
  • DA
  • BTT
    • 転送するバイト数を指定する
    • このレジスタへのWriteでDMA転送がキックされる

2.2. 制御の流れ

SWは以下の処理を行えば良い

  1. 割り込みハンドラの設定
  2. CDMASR.IDLE=1を確認
  3. CDMACR.IOC_IrqEnとCDMACR.IrqEnに1をセット
  4. SAに転送元のアドレスをセット
  5. DAに転送先のアドレスをセット
  6. BTTに転送するバイト数をセットし,DMA転送を開始させる
  7. 割り込みが上がるのを待つ
  8. 割り込み処理を行う
    • CDMASR.Err_Irq, CDMASR.IOC_Irqを確認し,割り込み要因をクリアする
  9. 上記2に戻る

3. Xilinx SDK で DMACを操作するSWを書く

2つバッファを確保し,DMAでデータを転送するSWを書く. 今回は勉強のため, AXI CDMA用の関数(xaxicdma.h内で宣言)は使わず,制御レジスタを自分で操作する.

コード全部を載せると長くなるので完成したコードは ZYBOで遊ぶ02 · GitHub に置いた.

DMACキック後は割り込みが上がるのを待つようにし,割り込みハンドラでは割り込み要因のクリアを行った後,転送先のバッファのデータをUARTを使ったPC側のターミナルに表示させる.

3.1. プロジェクト作成

File->New->Application Projectをクリック.
Project nameを埋めて,Nextボタンをクリック.
TemplatesにHello Worldを指定してFinishボタンをクリック.

3.2. 制御レジスタ関連の定数の定義

xaxicdma.h にCDMAを操作するための関数,構造体,定数が宣言されているが,今回はこれらを使わずに自分で定数の定義をおこない,制御レジスタをひとつひとつ読み書きして操作する.

#define CDMA_BASE_ADDR XPAR_AXI_CDMA_0_BASEADDR
#define CDMA_CR_ADDR  CDMA_BASE_ADDR+0x00
#define CDMA_SR_ADDR  CDMA_BASE_ADDR+0x04
#define CDMA_SA_ADDR  CDMA_BASE_ADDR+0x18
#define CDMA_DA_ADDR  CDMA_BASE_ADDR+0x20
#define CDMA_BTT_ADDR CDMA_BASE_ADDR+0x28

#define CDMA_CR_ERR_IRQ_MASK 0x00004000
#define CDMA_CR_IOC_IRQ_MASK 0x00001000

#define CDMA_SR_ERR_IRQ_MASK 0x00004000
#define CDMA_SR_IOC_IRQ_MASK 0x00001000
#define CDMA_SR_DEC_ERR_MASK 0x00000040
#define CDMA_SR_SLV_ERR_MASK 0x00000020
#define CDMA_SR_INT_ERR_MASK 0x00000010
#define CDMA_SR_IDLE_MASK    0x00000002

3.3. バッファの確保

転送元のバッファと転送先のバッファを以下の様に静的に確保する.

volatile u8 src_buf[BUF_BYTE_NUM] __attribute__ ((aligned(32)));
volatile u8 dst_buf[BUF_BYTE_NUM] __attribute__ ((aligned(32)));

バス幅が64bitなので8Byteアライン指定で良いかと思ったが,8Byteアラインとするとキャッシュのinvalidateが一部うまくいかなかった(Xil_DCacheInvalidateRange関数の説明を読めば謎が解けるはずなので読む).
キャッシュラインサイズに合わせておけばキャッシュ操作もうまくいくと考え,Cortex-A9のキャッシュラインサイズは32Byteのようなので32Byteアラインとした. BUF_BYTE_NUMは自分で定義した定数で,今回は128Byteとした.

3.4. バッファに初期値をセットする

以下のset_data関数で転送元のバッファには0, 1, 2,・・・という様なインクリメントした値を順にセットし, 転送先のバッファにはall 0 データをセットする.

void set_data(u8 *buf_ptr, u32 num, int mode/* 0:incr/1:zero*/)
{
    int i;

    print("\n[set_data]\n\r");
    for (i = 0; i < num; i++) {
        buf_ptr[i] = (mode) ? 0 : i;
    }

    // flush data
    Xil_DCacheFlushRange((u32)buf_ptr, num);
}

重要なのが最後の Xil_DCacheFlushRange関数の実行.
この関数で明示的にキャッシュフラッシュをしないと,転送元のバッファにセットしたデータがDRAMまで書き込まれず,DMA転送実行後の結果が意図した通りにならない.
最初はキャッシュの存在を意識していなかったため,データが化ける?????としばらく悩んだが,キャッシュをフラッシュすることで解決.

3.5. DMAキック前のバッファ内のデータ確認

転送元と転送先のバッファ内のデータをprint_buf関数で,PCのターミナルに表示させ,値を確認する.

3.6. 割り込み設定

SetupIntrSystem関数に記述.
基本的には ZYBOで遊ぶ01:簡易タイマーIPの自作(4) - ThuruThuruToru’s blog と同じだが,

typedef struct {
  u8 *src_buf_ptr;
  u8 *dst_buf_ptr;
} BufSt;

という転送元,転送先バッファのポインタをまとめた構造体を定義し,割り込みハンドラにこの構造体のポインタを渡すようにしている.

3.7. 割り込みハンドラ

cdma_int_handler関数に記述.
CDMAが割り込みをあげる度にこの関数が読み出される.
引数には,割り込み設定で指定した BufSt構造体のポインタが渡される.

void cdma_int_handler(void *callback)
{
  u32 data32;
  char str[STR_BYTE_NUM];

  print("\n[cdma_int_handler]\n\r");

  // check SR
  data32 = Xil_In32(CDMA_SR_ADDR);
  snprintf(str, BUF_BYTE_NUM, "CDMA_SR = %lx\n\r", data32);
  print(str);
  if (data32 & CDMA_SR_ERR_IRQ_MASK)
    print("ERR_IRQ\n\r");
  if (data32 & CDMA_SR_IOC_IRQ_MASK)
    print("IOC_IRQ\n\r");
  if (data32 & CDMA_SR_DEC_ERR_MASK)
    print("DEC_ERR\n\r");
  if (data32 & CDMA_SR_SLV_ERR_MASK)
    print("SLV_ERR\n\r");
  if (data32 & CDMA_SR_INT_ERR_MASK)
    print("INT_ERR\n\r");
  if (data32 & CDMA_SR_IDLE_MASK)
    print("IDLE\n\r");

  // clear interrupt
  print("clear interrupt\n\r");
  data32 = CDMA_SR_ERR_IRQ_MASK | CDMA_SR_IOC_IRQ_MASK;
  Xil_Out32(CDMA_SR_ADDR, data32);

  // check SR
  data32 = Xil_In32(CDMA_SR_ADDR);
  snprintf(str, BUF_BYTE_NUM, "CDMA_SR = %lx\n\r", data32);
  print(str);

  // dcache invalidate
  print("dcache invalidate\n\r");
  Xil_DCacheInvalidateRange((unsigned int)((BufSt *)callback)->dst_buf_ptr, BUF_BYTE_NUM);

  // print dst buffer
  print_buf(((BufSt *)callback)->dst_buf_ptr, BUF_BYTE_NUM);
}

処理としては,割り込みクリア,データキャッシュinvalidate,転送先バッファ内のデータの表示をしている.

初期データの確認を3.5で行ったので,転送先バッファのデータはデータキャッシュに格納された状態となっている.そのため,データキャッシュのinvalidateをしないと,DMACがDRAMに転送したデータではなく,キャッシュにヒットした古いデータを読み出してしまう.
ここもはまった.

3.8. CDMA初期化

init_cdma関数に以下の処理を記述.

  • CDMASR, CDMACR を読んでIDLEであることを確認(IDLEになるまでポーリング)
  • CDMACR.IOC_IrqEnとCDMACR.IrqEnに1をセット

3.9. DMACキック

trans_data関数内に,CDMAの制御レジスタへ転送元,転送先アドレスと転送バイト数を書き込む処理を記述.

3.10. 動かす

転送先のバッファに転送元のバッファのデータが書き込まれることが確認できるはず.

qと打つとプログラムが終了するようにしている.

まとめ

AXI CDMA を使って DRAM<->DRAM のデータ転送を行ってみた.
次は AXI VDMA(AXI4 <-> AXI4-Stream)を使ってみる予定.


ZYBOで遊ぶ02:AXI CDMA IPを使ってみた(1)

ZYBOで遊んだメモ

ZYBOで遊ぶ02:AXI CDMA IPを使ってみた(1) - ThuruThuruToru’s blog
ZYBOで遊ぶ02:AXI CDMA IPを使ってみた(2) - ThuruThuruToru’s blog



やったこと

AXI CDMA IP(AXI4 <-> AXI4) を使ってみたのでメモ書き.
今回はRTLはいじらず,PS部にAXI CDMA IPをつないだだけのシステムを作り, Xilinx SDKでDMACを操作するSWを書いて DRAM<->DRAM のデータ転送をしてみた.

1. VivadoでHWデザイン作成

1.1. ZYNQ7 Processing Systemの追加

Vivadoのプロジェクトを作成した後,Generate Block Design をクリックし,Add IP で ZYNQ7 Processing Systemをダブルクリック.

作成された ZYNQ7 Processing System をダブルクリックして以下の設定を行う.

  • Import XPS Settings で ZYBO_zynq_def.xml を指定(いつもと同じ)
  • PS-PL Configuration で S AXI HP0 interface を選択する
    • このSlaveポートにAXI CDMAがマスタとしてつながる
  • Interrupts で IRQ_F2P を選択する
    • AXI CDMAの割り込み用

f:id:ThuruThuruToru:20151113212838p:plain

f:id:ThuruThuruToru:20151113213203p:plain

1.2. AXI CDMAの追加

Add IP でAXI Central Direct Memory Accessをダブルクリック.
作成された AXI Central Direct Memory Access をダブルクリックして以下の設定を行う.

  • Enable Scatter Gather のチェックを外す
    • 外さなくてもいいが,今回はScatter Gatherを使わない
  • Write/Read Data Width を 64にする
    • S AXI HP0 はデータが64bit幅なので効率良く転送できるように合わせる

f:id:ThuruThuruToru:20151113214051p:plain

1.3. IPをつなぐ

Processor System と AXI CDMA をつなぐ.

f:id:ThuruThuruToru:20151113214105p:plain

ここまでで上の図のようになっているので,上部に表示されている「Run Block Automation」と「Run Connection Automation」を実行する.
割り込み信号は自動でつながらないので,Processing System の IRQ_F2P と AXI CDMAcdma_introut をマウスでつなぐ.
Validate Designを実行し,デザインに問題がないことを確認.

f:id:ThuruThuruToru:20151113214120p:plain

アドレスエディタを開いてみると,自動で以下の様に割り当てられていた.問題ないのでいじらない.

f:id:ThuruThuruToru:20151113214129p:plain

1.4. Bitstream 作成

Sourcesタブ内のdesign_1(自分でつけたBlock Design名)を右クリックし,Create HDL Wrapperを指定し,Topモジュールを自動生成する.

Project Manager->Program and Debug->Generate Bitstreamをクリックし,Bitstream作成.

File->Export->Export Hardwareをクリックし,Include bitstreamにチェックを入れてOKボタンを押す.

File->Launch SDKをクリックして,Xilinx SDK(Eclipse)を立ち上げる.

VivadoでのHWデザインの作成は完了.ここからはXilinx SDKでSWを書いていく.

ZYBOで遊ぶ02:AXI CDMA IPを使ってみた(2) - ThuruThuruToru’s blog に続く


ZYBOで遊ぶ01:簡易タイマーIPの自作(4)

ZYBOで遊ぶ01:簡易タイマーIPの自作(3) - ThuruThuruToru’s blog の続き


ZYBOで遊ぶ01:簡易タイマーIPの自作(1) - ThuruThuruToru’s blog
ZYBOで遊ぶ01:簡易タイマーIPの自作(2) - ThuruThuruToru’s blog
ZYBOで遊ぶ01:簡易タイマーIPの自作(3) - ThuruThuruToru’s blog
ZYBOで遊ぶ01:簡易タイマーIPの自作(4) - ThuruThuruToru’s blog



10. 割り込み処理の記述

自作したタイマーが割り込みを上げたときの割り込み処理の記述と割り込みハンドラの登録処理を書いていく.
ちゃんとマニュアルを読めば手順が書いてあるのだと思うが,横着をして以下のサンプルから必要そうな部分を取り出して動かしてみた.

SDK/2014.4/data/embeddedsw/XilinxProcessorIPLib/drivers/
axidma_v8_0/examples/xaxidma_example_simple_intr.c

10.1. 割り込みハンドラの記述

割り込みが上がったときにCPUにやってもらう仕事を書く.
今回は,

  • [割り込みステータス]を確認し,割り込みクリア
  • タイマーを再実行

という処理を書いてみた.
今回は使っていないが,関数の引数には割り込みハンドラ登録時に任意のポインタを渡すようにできる.

void mytimer_int_handler(void *callback)
{
    u32 read_data;

    print("\n\n\rmytimer_int_handler--------------\n\r");
    read_data = read_and_print(MYTIMER_INT_STAT_ADDR, "[INT_STAT]");
    if (read_data == 1) {
        print("Clear Interrupt\n\r");
        Xil_Out32(MYTIMER_INT_STAT_ADDR, 0x01);

        print("Rerun Timer\n\r");
        Xil_Out32(MYTIMER_STAT_CTRL_ADDR, 0x01);
    }
    print("---------------------------------\n\n\r");
}

10.2. 割り込みハンドラの登録

以下の関数を順番に実行していけば良さそう(後でちゃんとマニュアルを読みたい).
作成した割り込みハンドラを登録・有効化するのは,XScuGic_ConnectとXScuGic_Enableなので,この部分だけ書き換えればその他の部分はこの先もそのまま使いまわせそう.
割り込みをあげるIPが複数あるケースでは,XSuGic_ConnectとXScuGic_Enableを複数書けば良い.

  • XScuGic_LookupConfig
  • XSuGic_CfgInitialize
  • XScuGic_Connect
  • XScuGic_Enable
  • Xil_ExceptionInit
  • XilExceptionRegisterHandler
  • Xil_ExceptionEnable

XPAR_FABRIC_MYTIMER_0_INTERRUPT_INTR という定数は,xparameters.h で自動で定義されているmyTimerの割り込み番号. XScuGic_Connectの最後の引数にはNULLを指定しているので,割り込みハンドラには何もデータを渡していない.
この引数に変数のポインタとかを指定すれば,割り込みハンドラ側でデータを参照できる.

割り込みハンドラの登録・有効化は以下の様になった.

#include "xscugic.h"
#include "xil_exception.h"
int SetupIntrSystem(XScuGic *IntcInstancePtr)
{
    int Status;

    XScuGic_Config *IntcConfig;


    /*
     * Initialize the interrupt controller driver so that it is ready to
     * use.
     */
    IntcConfig = XScuGic_LookupConfig(XPAR_SCUGIC_SINGLE_DEVICE_ID);
    if (IntcConfig == NULL) {
        print("Failed XScuGic_LookupConfig\n\r");
        return XST_FAILURE;
    }

    Status = XScuGic_CfgInitialize(IntcInstancePtr, IntcConfig,
                                   IntcConfig->CpuBaseAddress);
    if (Status != XST_SUCCESS) {
        print("Failed XScuGic_CfgInitialize\n\r");
        return XST_FAILURE;
    }

    /*
     * Connect the device driver handler that will be called when an
     * interrupt for the device occurs, the handler defined above performs
     * the specific interrupt processing for the device.
     */
    Status = XScuGic_Connect(IntcInstancePtr,
                         XPAR_FABRIC_MYTIMER_0_INTERRUPT_INTR,
                             (Xil_InterruptHandler)mytimer_int_handler,
                             NULL);
    if (Status != XST_SUCCESS) {
        print("Failed XScuGic_Connect\n\r");
        return XST_FAILURE;
    }

    XScuGic_Enable(IntcInstancePtr,
                   XPAR_FABRIC_MYTIMER_0_INTERRUPT_INTR);

    /* Enable interrupts from the hardware */
    Xil_ExceptionInit();
    Xil_ExceptionRegisterHandler(XIL_EXCEPTION_ID_INT,
                                 (Xil_ExceptionHandler)XScuGic_InterruptHandler,
                                 (void *)IntcInstancePtr);
    Xil_ExceptionEnable();

    return XST_SUCCESS;
}

11. 完成したFW

main関数では,

  • 割り込み設定
  • タイマー起動
  • 'q'をプレスされると終了するループ

を行い,割り込みハンドラでは,割り込みをクリアし,タイマーを再起動するようにした.

#include <stdio.h>
#include "platform.h"
#include "xil_io.h"
#include "xscugic.h"
#include "xil_exception.h"


#define MAX_SIZE 1024
#define MYTIMER_INT_STAT_ADDR  XPAR_MYTIMER_0_S00_AXI_LITE_BASEADDR+0x0
#define MYTIMER_STAT_CTRL_ADDR XPAR_MYTIMER_0_S00_AXI_LITE_BASEADDR+0x4
#define MYTIMER_COUNTER_ADDR   XPAR_MYTIMER_0_S00_AXI_LITE_BASEADDR+0x8
#define MYTIMER_DEBUG0_ADDR    XPAR_MYTIMER_0_S00_AXI_LITE_BASEADDR+0xC

u32 read_and_print(u32 Addr, char *head_str)
{
    u32 read_data;
    char str[MAX_SIZE];

    read_data = Xil_In32(Addr);
    snprintf(str, MAX_SIZE, "%s = %lx\n\r", head_str, read_data);
    print(str);

    return read_data;
}

void mytimer_int_handler(void *callback)
{
    u32 read_data;

    print("\n\n\rmytimer_int_handler--------------\n\r");
    read_data = read_and_print(MYTIMER_INT_STAT_ADDR, "[INT_STAT]");
    if (read_data == 1) {
        print("Clear Interrupt\n\r");
        Xil_Out32(MYTIMER_INT_STAT_ADDR, 0x01);

        print("Rerun Timer\n\r");
        Xil_Out32(MYTIMER_STAT_CTRL_ADDR, 0x01);
    }
    print("---------------------------------\n\n\r");
}

int SetupIntrSystem(XScuGic *IntcInstancePtr)
{
    int Status;

    XScuGic_Config *IntcConfig;


    /*
     * Initialize the interrupt controller driver so that it is ready to
     * use.
     */
    IntcConfig = XScuGic_LookupConfig(XPAR_SCUGIC_SINGLE_DEVICE_ID);
    if (IntcConfig == NULL) {
        print("Failed XScuGic_LookupConfig\n\r");
        return XST_FAILURE;
    }

    Status = XScuGic_CfgInitialize(IntcInstancePtr, IntcConfig,
                                   IntcConfig->CpuBaseAddress);
    if (Status != XST_SUCCESS) {
        print("Failed XScuGic_CfgInitialize\n\r");
        return XST_FAILURE;
    }

    /*
     * Connect the device driver handler that will be called when an
     * interrupt for the device occurs, the handler defined above performs
     * the specific interrupt processing for the device.
     */
    Status = XScuGic_Connect(IntcInstancePtr,
                             XPAR_FABRIC_MYTIMER_0_INTERRUPT_INTR,
                             (Xil_InterruptHandler)mytimer_int_handler,
                             NULL);
    if (Status != XST_SUCCESS) {
        print("Failed XScuGic_Connect\n\r");
        return XST_FAILURE;
    }

    XScuGic_Enable(IntcInstancePtr,
                   XPAR_FABRIC_MYTIMER_0_INTERRUPT_INTR);

    /* Enable interrupts from the hardware */
    Xil_ExceptionInit();
    Xil_ExceptionRegisterHandler(XIL_EXCEPTION_ID_INT,
                                 (Xil_ExceptionHandler)XScuGic_InterruptHandler,
                                 (void *)IntcInstancePtr);
    Xil_ExceptionEnable();

    return XST_SUCCESS;
}

int main()
{
    int status;
    int c;
    char str[MAX_SIZE];
    XScuGic Intc;

    init_platform();

    status = SetupIntrSystem(&Intc);
    if (status != XST_SUCCESS) {
        return status;
    }

    // run timer
    print("Run Timer------------------------------------------\n\r");
    Xil_Out32(MYTIMER_COUNTER_ADDR, 0x10000000);
    Xil_Out32(MYTIMER_STAT_CTRL_ADDR, 0x00000001);

    // wait for 'q' is pressed
    while (1) {
        c = inbyte();
        snprintf(str, MAX_SIZE, "%c\n\r", c);
        print(str);
        if (c == 'q')
            break;
    }

    print("\n\n\rquit\n\r");

    cleanup_platform();
    return 0;
}

11. まとめ

簡易タイマーIPを自作し,PS部から自作IPを操作するFWを書いた.
CPUからIPの制御レジスタアクセス,割り込みハンドラの登録ができるようになった.
これで,自作IPを制御できるようになったので,次はPS部からDMACを起動しデータ転送を行ってみる予定.


ZYBOで遊ぶ01:簡易タイマーIPの自作(3)

ZYBOで遊ぶ01:簡易タイマーIPの自作(2) - ThuruThuruToru’s blog の続き


ZYBOで遊ぶ01:簡易タイマーIPの自作(1) - ThuruThuruToru’s blog
ZYBOで遊ぶ01:簡易タイマーIPの自作(2) - ThuruThuruToru’s blog
ZYBOで遊ぶ01:簡易タイマーIPの自作(3) - ThuruThuruToru’s blog
ZYBOで遊ぶ01:簡易タイマーIPの自作(4) - ThuruThuruToru’s blog


8. Hello World をHost PC上のターミナルに表示

8.1. Application Projectの作成

まずは,Application Projectを作成し,Templatesのコードを実行してみる.

File->New->Application Projectをクリックする.
Project nameをtestとし,Nextをクリック.Templatesに Hello Worldを選択し,Finishをクリック.
Project Explorerにtestとtest_bspが作成される.

Project Explorer->test->src->helloworld.cをダブルクリックする.
main関数内に UARTを使ってHost PC上のターミナルに"Hello World"を表示するコードが書かれている.

8.2. Hello Worldを実行してみる

ソースを編集すると自動でビルドされるようになっているようなので,helloworld.cを一部変更->元に戻して保存し,実行ファイルを生成する.

ZYBOの電源が入っており,PCとつながっている状態で,Xilinx Tools->Program FPGAをクリック.
Programボタンを押せば,FPGAに構成情報が書き込まれる.

下部にあるTerminalタブ内のSettingsをクリックし,Connection TypeをSerial,Baud Rateを115200に設定(Portも確認). cuコマンドをxterm等の自分の好きなターミナルで実行してシリアル接続しても良い.

Run->Debug As->2 Launch on Hardware(System Debugger)をクリック.
Debug画面になるので,上部にあるResumeボタンをクリックするとプログラムが実行され,"Hello World"がTerminalに表示される.

9. 自作Timerを動かす

9.1. アドレスマップ

実はVivadoのBlock Diagram内のAddress Editorタブ内でmyTimerのアドレスマップの割り当てを決めることができる.今回は自分でいじっていないのでデフォルトで 0x43C0_0000から64K割り当てられている模様. f:id:ThuruThuruToru:20151031232958p:plain

つまり,各制御レジスタのアドレスは以下のようになっている.

  • 0x43c0_0000: [割り込みステータス]
  • 0x43c0_0004: [ステータス/コントロール]
  • 0x43c0_0008: [カウンタ値]
  • 0x43c0_000C: [デバッグ0]

BASEADDRESSである0x43C0_0000は,
Project Explorer->test_bsp->ps7_cortexa9_0->include内のxparameters.h に自動でdefineされるようなのだがなぜか記述がない・・・

色々試して見たところ,system.mss の"Modify this BSP's Settings"をクリックし,Overview->driversのmyTimerのDriver列の値をgenericに変更したところ,xparameters.h内にBASEADDRESSの定義が追記された.

#define XPAR_MYTIMER_0_S00_AXI_LITE_BASEADDR 0x43C00000
#define XPAR_MYTIMER_0_S00_AXI_LITE_HIGHADDR 0x43C0FFFF

自分で定義しても良いが,何か気持ち悪い.
system.mss の自作IPの所に他のIPみたいに Documentation, Import Examples を表示させる方法も知りたいし,まじめにマニュアルを読もうと思う. f:id:ThuruThuruToru:20151101003706p:plain

9.2. 制御レジスタへのアクセステスト

xil_io.h に定義されている,Xil_Out32, Xil_In32で制御レジスタのWrite/Readができた.
Xil_Out32のコードを見てみると,

*(volatile u32 *) OutAddress = Value;

となっており,単純に指定されたアドレスに値を入れている.
これでAXI-Liteのライトが発行されている模様.

myTimerの[デバッグ0]レジスタに値を書いて,その値が読めることを確認するコードは以下の通り.

#include <stdio.h>
#include "platform.h"
#include "xil_io.h"

#define MAX_SIZE 1024
#define MYTIMER_INT_STAT_ADDR  XPAR_MYTIMER_0_S00_AXI_LITE_BASEADDR+0x0
#define MYTIMER_STAT_CTRL_ADDR XPAR_MYTIMER_0_S00_AXI_LITE_BASEADDR+0x4
#define MYTIMER_COUNTER_ADDR   XPAR_MYTIMER_0_S00_AXI_LITE_BASEADDR+0x8
#define MYTIMER_DEBUG0_ADDR    XPAR_MYTIMER_0_S00_AXI_LITE_BASEADDR+0xC

int main()
{
    u32 read_data;
    char str[MAX_SIZE];

    init_platform();

    print("Hello World\n\r");

    // write/read myTimer[DEBUG0]
    Xil_Out32(MYTIMER_DEBUG0_ADDR, 0x12345678);
    read_data = Xil_In32(MYTIMER_DEBUG0_ADDR);
    snprintf(str, MAX_SIZE, "%lx\n\r", read_data);
    print(str);

    cleanup_platform();
    return 0;
}

9.3. 自作Timerの動作確認

下記のコードでタイマーを動かし,各制御レジスタの値を観察してみた.

#include <stdio.h>
#include "platform.h"
#include "xil_io.h"

#define MAX_SIZE 1024
#define MYTIMER_INT_STAT_ADDR  XPAR_MYTIMER_0_S00_AXI_LITE_BASEADDR+0x0
#define MYTIMER_STAT_CTRL_ADDR XPAR_MYTIMER_0_S00_AXI_LITE_BASEADDR+0x4
#define MYTIMER_COUNTER_ADDR   XPAR_MYTIMER_0_S00_AXI_LITE_BASEADDR+0x8
#define MYTIMER_DEBUG0_ADDR    XPAR_MYTIMER_0_S00_AXI_LITE_BASEADDR+0xC

u32 read_and_print(u32 Addr, char *head_str)
{
    u32 read_data;
    char str[MAX_SIZE];

    read_data = Xil_In32(Addr);
    snprintf(str, MAX_SIZE, "%s = %lx\n\r", head_str, read_data);
    print(str);

    return read_data;
}

int main()
{
    u32 read_data;

    init_platform();

    read_and_print(MYTIMER_INT_STAT_ADDR, "[INT_STAT]");
    read_and_print(MYTIMER_STAT_CTRL_ADDR, "[STAT_CTRL]");
    read_and_print(MYTIMER_COUNTER_ADDR, "[COUNTER]");

    // run timer
    print("Run Timer------------------------------------------\n\r");
    Xil_Out32(MYTIMER_COUNTER_ADDR, 0x10000000);
    Xil_Out32(MYTIMER_STAT_CTRL_ADDR, 0x00000001);
    do {
        read_and_print(MYTIMER_INT_STAT_ADDR, "[INT_STAT]");
        read_and_print(MYTIMER_COUNTER_ADDR, "[COUNTER]");
        read_data = read_and_print(MYTIMER_STAT_CTRL_ADDR, "[STAT_CTRL]");
        print("\n\r");
    } while(read_data==1);
    print("---------------------------------------------------\n\r");

    read_and_print(MYTIMER_INT_STAT_ADDR, "[INT_STAT]");
    read_and_print(MYTIMER_STAT_CTRL_ADDR, "[STAT_CTRL]");
    read_and_print(MYTIMER_COUNTER_ADDR, "[COUNTER]");

    cleanup_platform();
    return 0;
}

ZYBOで遊ぶ01:簡易タイマーIPの自作(4) - ThuruThuruToru’s blog に続く


ZYBOで遊ぶ01:簡易タイマーIPの自作(2)

ZYBOで遊ぶ01:簡易タイマーIPの自作(1) - ThuruThuruToru’s blog の続き


ZYBOで遊ぶ01:簡易タイマーIPの自作(1) - ThuruThuruToru’s blog
ZYBOで遊ぶ01:簡易タイマーIPの自作(2) - ThuruThuruToru’s blog
ZYBOで遊ぶ01:簡易タイマーIPの自作(3) - ThuruThuruToru’s blog
ZYBOで遊ぶ01:簡易タイマーIPの自作(4) - ThuruThuruToru’s blog


4. Create Block Design

左側のIP Integrator -> Create Block Design をクリックし,Design nameを適当に決めてOKボタンをクリックする.

Diagram というタブが現れるので,ここにPS部, 自作したIP, IO を追加・接続してデザインを作っていく.

4.1. PS部

Diagram タブ内で右クリック->Add IPを選択.
現れたWindowから「ZYNQ7 Processing System」をダブルクリックする. f:id:ThuruThuruToru:20151031221917p:plain

現れたブロックを右クリック->Customize Blockを選択.
Re-customize IPという画面が現れるので以下の設定をする.

  • Import XPS Settings をクリックし,ZYBO用の設定ファイルを指定する

  • 左側のInterruptsをクリック

    • Fabric Interruptsにチェック
    • Fabric Interruptsの左側●をクリックすると表示されるPL-PS Interrupt PortsIRQ_F2Pにチェック
  • OKをクリックして Re-customize IPを閉じる

正しく設定できていれば,以下の図の様になる. f:id:ThuruThuruToru:20151031223444p:plain

Diagramタブの上の方に「Run Block Automation」という青文字がでているのでクリックする.
Run Block Automationという画面がでるのでOKをクリック.
以下の図の様にDDRとFIXED_IOというポートが作成される. f:id:ThuruThuruToru:20151031223622p:plain

4.2 自作したタイマーIPをPS部と接続する

Diagramタブ内で右クリック->Add IPを選択.
myTimerを探してダブルクリック. f:id:ThuruThuruToru:20151031224155p:plain

Diagramタブの上の方に「Run Connection Automation」という青文字がでているのでクリック,表示された画面のOKボタンをクリックし,タイマーIPとPS部を自動接続する.

右クリック->Regenerate Layoutを選択すると以下の様になった.
Processor System ResetとAXI Interconnectというブロックが自動で追加され,AXI IF, Clock, Resetが自動で接続されている. f:id:ThuruThuruToru:20151031224625p:plain

myTimerを見ると,interruptとledがまだどこにもつながっていない.

interruptはZYNQ7 Processing SystemのIRQ_F2Pにつなげば良いので,マウスで2つの信号をつなぐようにドラッグする.

ledはIOへの接続なので,Diagramタブ内の白い部分で右クリック->Create PortをクリックしてOutputポートを作成する.Port nameをLED, DirectionをOutput, TypeをDataとしてOKボタンをクリック.
LEDポートが作られるので,マウスを使ってmyTimerのledポートと接続する. f:id:ThuruThuruToru:20151031225523p:plain

Diagramタブ内の白い部分で右クリック->Validate Designをクリックしてデザインに問題がないことを確認する.

5. Constraints 設定

Flow Navigator内のProject Manager->Add Sourcesをクリック.
「Add or create constraints」を選択する.
Add Filesを選び,ZYBO_Master.xdc(digilentのZYBOのページ下部からダウンロード)を選択し,Finishボタンをクリック.

SourcesタブからConstraints->constrs_1->ZYBO_Master.xdcをダブルクリック.
全ての設定がコメントアウトされた状態になっているので, IO_L23P_T3_35を探し,

set_property PACKAGE_PIN M14 [get_ports {LED}]
set_property IOSTANDARD LVCMOS33 [get_ports {LED}]

と修正する.

6. Create HDL Wrapper

SourcesタブからDesign Sources->design_1を右クリックし,Create HDL Wrapperを選択して,TopとなるHDLを作成する.

7. Synthesis, Implementation, Generate Bitstream

Flow Navigator内のProgram and Debug->Generate Bitstreamをクリックし,Synthesis, Implementation, Generate Bitstream をいっきに実行.

BitStreamの生成まで無事完了したら,File->Export->Export Hardwareをクリック.
Include bitstream にチェックを入れてOKをクリック.

File->Launch SDK クリックし,Eclipseを起動する.
ここからは,Eclipse上でFarmwareを書いていく.

ZYBOで遊ぶ01:簡易タイマーIPの自作(3) - ThuruThuruToru’s blog に続く


ZYBOで遊ぶ01:簡易タイマーIPの自作(1)

ZYBOで遊んだメモ

ZYBOで遊ぶ01:簡易タイマーIPの自作(1) - ThuruThuruToru’s blog
ZYBOで遊ぶ01:簡易タイマーIPの自作(2) - ThuruThuruToru’s blog
ZYBOで遊ぶ01:簡易タイマーIPの自作(3) - ThuruThuruToru’s blog
ZYBOで遊ぶ01:簡易タイマーIPの自作(4) - ThuruThuruToru’s blog



OS: Ubuntu 12.04.5 LTS(32bit)
Vivado: v2014.4

やること

Vivadoの使い方の練習として,簡単なタイマーIPを作成する.

Xilinx SDKを使い,作成したタイマーを制御するコードを書く.
自作したIPの制御レジスタにPS部からアクセスする方法と,PL部からの割り込みを処理する方法を身につける.

1. 作成するタイマーの仕様

1.1. 入出力

  • AXI4-Lite(slave) IF
  • 1bitの割り込み出力
  • LEDにつなぐ1bitの出力

1.2. 制御レジスタ

  • 0x00:割り込みステータス
  • 0x04:ステータス/コントロール
    • bit[0]: Run(RW)
      • 1'b0:Stop/1'b1:Run
      • Default: 1'b0
  • 0x08:カウンタ値
    • bit[31:0]: 現在のカウンタ値(RW)
      • Default: 32'hFFFF_FFFF
  • 0x0C: デバッグ0
    • bit[31:0]: 読み書き可能な意味のないレジスタ(RW)
      • Default: 32'h0000_0000

1.3. 機能

  • [カウンタ値]にセットされたクロック数をカウントし,カウンタが0になったら割り込みをあげる
  • [ステータス/コントロール]bit[0]の値がLEDに出力される

1.4. 制御方法

  1. [カウンタ値]にカウントしたいクロック数を書き込む
  2. [ステータス/コントロール]のbit[0]に1'b1を書き込み,カウントを開始する
  3. カウンタの値が0になると割り込みがあがりカウントが止まるので,[割り込みステータス]bit[0]を1 Clearする
    • カウンタ値は前回Runを開始した値にリセットされるので,前回と同じ値をカウントしたい場合は手順2から実行すれば良い

2. Vivado プロジェクトを作る

Vivadoを起動してプロジェクトを作る

  • Create New Project
    • Project name: timer_project
    • Project Type:RTL Project
    • Source, IP, constraintは何も追加しない
    • Default Part: xc7z010clg400-1

3. タイマーIPを自作する

3.1. IPを新規作成する

toolバーの Edit->Create and Package New IP をクリック

  • Create a new AXI4 peripheralを選択
  • Peripheral Details
    • Name: myTimer
    • Description: My new AXI Timer IP
  • Add Interfaces
    • Enable Interrupt Supportはチェックしない
    • Name: S00_AXI_LITE
    • Data Width: 32Bits
    • Number of Registers: 4
  • Create Peripheral
    • Edit IPにチェック

3.2. 生成されたソースを修正する

生成された myTimer_v1_0_S00_AXI_LITE.v と myTimer_v1_0.v を仕様に合わせて修正する.

ざっくり以下の修正をする.

  • 出力ポートに interrupt, led を追加
  • 制御レジスタ slv_reg0~3 の処理を修正

追加する入出力信号は,「Users to add ports here」というコメントの下に書かないとIPのGUI表示がうまくいかないので注意する.

3.3. Package IP

左側に表示されているProject Manager -> Package IPをクリックし,表示された「Package IP - myTimer」タブ内のフォームを埋めていく

  • Identification
    • Vendor等を好きなように埋める
  • Compatibility
    • Life Cycle を自分の好きなステータスに変える
  • File Groups
    • ここにDriver等を追加できるようだけど,今回はいじらない
  • Customization Parameters
    • 「managed ~」 ていうのが上部に表示されていたらクリック
  • Ports and Interfaces
    • ソースコードに追加した interrupt, led が表示されていることを確認
  • Customization GUI
    • 以下の様になっていることを確認 f:id:ThuruThuruToru:20151031215007p:plain

Review and Package の Package IP をクリックすればIPの作成完了.

これ以降は,timer_project を開いているWindowでサブシステムの設計をしていく.

ZYBOで遊ぶ01:簡易タイマーIPの自作(2) - ThuruThuruToru’s blog に続く


Nucleo-F401RE(mbed)で遊ぶ01: Lチカ

昨年買ったまま放置していたNucleoで遊びはじめたのでメモ

購入してから基本的な使い方

購入したのは、STMicroelectronicsのNucleo-F401REという評価基板。STM32F401REというマイコンが乗っている。開発環境はmbedに対応しており、web上のIDEで開発ができるので非常にお手軽。また、Arduino Uno Revision3 互換であるため、Arduinoのシールドを簡単に接続できる。 マルツで1450円(税抜)で購入したが、今は円安のせいかもう少し高くなっている模様。

基本的な使い方は以下のサイトでわかりやすく紹介されているので、これを参考にした。

  • 基板をUSBでPCに接続
  • USBメモリと同じように、NUCLEOという名前のデバイスが認識されるので、中に入っているmbed.htmをブラウザで開く
  • ユーザ登録を行う
  • ファームウェアを更新する
  • 「Open mbed Compiler」というボタンをクリックしてIDEを開く
  • プログラムを書いてコンパイル
  • コンパイルに成功すると、バイナリのダウンロード画面が現れる
  • バイナリを、USBメモリに保存するのと同じようにNUCLEOというデバイスに保存
  • プログラムがマイコン上で動き出す

blog.cloudninja.asia

公式のチュートリアル動画もある。 www.youtube.com

Lチカのプログラムを作成

IDEを開き、Newボタンをクリックする。ダイアログが現れるので、templateタブをクリックし、"Blinky LED test for the ST Nucleo boards"を選択しOKをクリックする。すると、プロジェクトが作成され、main.cppには以下のプログラムが記述されている。

#include "mbed.h"

DigitalOut myled(LED1);

int main() {
    while(1) {
        myled = 1; // LED is ON
        wait(0.2); // 200 ms
        myled = 0; // LED is OFF
        wait(1.0); // 1 sec
    }
}

このプログラムでは、LEDを0.2秒 ON ->1秒 OFF を繰り返す。 LEDの操作に用いているのが、"DigitalOut"というクラスで、"myled"というインスタンス名でインスタンスし、"LED1"というIOピンに接続するということをコンストラクタに渡している 。

"DigitalOut"などのライブラリについての説明は、以下のサイトに説明がある(日本語版の方が詳しい気がする)。
Homepage - Handbook | mbed
mbed 日本語リファレンス (私家版) | mbed

IOピンについては、以下のサイトに載っている図を見れば良い。
NUCLEO-F401RE | mbed
"青背景に白文字", "緑背景に白文字", infomartionの欄に記されている"additional labels"が指定できる。

あとはコンパイルして、バイナリをデバイスに保存してやればLチカが確認できる。