2019/03/31

Microblaze に豊四季タイニーBASIC を移植する

前回、Arty に Microblaze+カスタムペリフェラルを組み込んだ回路を作り上げました。
Microblaze に豊四季タイニーBASICを移植して、カスタムペリフェラルを動かしてみます。

BASIC のようなインタープリタ言語が入っているとカスタムペリフェラルの検証もお手軽ですね。

XSDK を直接立ち上げてプログラムを書くこともできるようですが、Vivado で前回作ったプロジェクト Arty_GSMB プロジェクトが立ち上がっているところから操作します。XSDK がインストールされていなかったら、しておきましょう。

Vivado のメニューの File から Launch SDK をクリックし、XSDK を立ち上げます。

場所はこのままで OK します。


XSDK の Project Explorer にはチュートリアルでエクスポートしたデータがあれば system_wrapper_hw_platform_0 と、今回新しく作った回路のデータ system_wrapper_hw_platform_1 が存在しています。なければ Vivado からエクスポートして、XSDK を立ち上げなおします。
system.hdf というファイルを開くと alu_double_0 がインスタンスされていることがわかります。無ければ‘Bitstream を更新していないとか、何か手順が抜けているはずです。見直してください。


ここまで出来ていればあとは移植するだけ。まずは Application Project を開いて、コードのひな型を作ります。豊四季タイニーBASIC を移植するにあたって UART は必須なので Hello World のテンプレートが役に立ちます。Hardware Platform を間違えないように。system_wrapper_hw_platform_1 ですよ。





このテンプレートのコードのトップは hello.c なので、main.c に名前変えました。なんとなく。変えなくてもいいですけど気持ち悪いので。

さ、下準備は整いました。あとは本当に移植するだけ。豊四季タイニーBASIC のソースコードを引っ張ってきます。いくつか種類があるのですが、移植が簡単だった Arduino 版がおすすめ。ところで上記のブログのコメント欄を見ないと気付かないのですが、確定版のソースは残念ながらバグがあります。これはパッチを当てて欲しかったです。詳細はコメントを見てください。

ソースは github の ココから落としてきました。展開すると basic.cpp と ttbasic.ino の二つのファイルがあり、普通の C での main.c に相当する ttbasic.ino ファイルから必要部分を main.c に移植し、basic.cpp をインポートして必要部分を追加修正します。

ちなみにこの basic.cpp、コメントが日本語なところが原因で、XSDK にインポートすると文字が化け化け。コンパイルすら通らなくなります。basic.c というファイル(中身は C なので cpp である必要はなしです) の空ファイルを作って、XSDK のエディタでこのファイルを開いているところに、別なエディタで開いた basic.cpp の中身をコピーしてペーストすると文字化けなどの問題がなくなります。

ttbasic.ino でやっていることは、乱数の初期化と basic() 関数の呼び出しです。basic() 関数は中で whle (1) の無限ループを回しているので呼び出すだけでいいです。従ってそのあとのコードは意味ないですが残しています。乱数の初期化はsrand() を呼び出しているのですが、ランダムなシードのソースが無いので(ADC の入力を使ってもいいとは思いますが)、仮に固定値 10 を与えています。main.c (hello.c) は以下のように書き換えます。

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

void basic(void);

int main()
{
    init_platform();

    srand(10);
    basic();

    cleanup_platform();
    return 0;
}
次に、basic.c を修正して豊四季タイニーBASIC が動作するようにしてみます。
ポイントはシリアル部分の移植で、これができればほぼ移植はできたも同然です。

まずはインクルードを追加修正します。

#include <stdio.h>
#include <stdlib.h>
#include <xuartlite_i.h>

ついでにエディションも変えておきます。起動時にメッセージで表示されます。

// TO-DO Rewrite these functions to fit your machine
#define STR_EDITION "MICROBLAZE"

ではシリアル周りを Microblaze に合わせます。移植は比較的簡単で、以下の三つの関数を移植するだけです。標準入出力は UART にアサインされているので、putchar(), getchar() に置き換えるだけです。c_kbhit() は UART の受信バッファにデータがあるかどうかを調べたい関数なので、UART のレジスタを直接調べます。実装している IP UART lite の仕様書を見ると STAT_REG の bit0 を見ればデータがあるかどうかわかります。下記のコードのようにすればバッファに有効なデータがあるかどうか確認することができます。UART0 マクロはもともと定義されていないので自分で定義していますが、これに関しては後で説明します。

void c_putch(char c){
    putchar(c);
}

char c_getch(){
    return getchar();
}

char c_kbhit(){
    return UART0->STAT_REG & XUL_SR_RX_FIFO_VALID_DATA;
}


XSDK で用意されたマクロや関数があるのですが好きなスタイルではないので、改めてマクロを定義します。ついでにカスタムペリフェラル alu_double のマクロの定義も追加します。

Project Explorer で作ったプロジェクト名 + _bsp 以下の microblaze_0/include/xparameters.h を探します。ここには alu_double だけでなくペリフェラルを含むいろいろなマクロ定義が書かれています。お目当ては UART と alu_double のベースアドレスです。ベースアドレスの定義を見つけたら定義を構造体をこのベースアドレスに割り当てます。

以下がその構造体とマクロの割り当て。

typedef struct {
    u32 RX_FIFO;
    u32 TX_FIFO;
    u32 STAT_REG;
    u32 CTRL_REG;
} UART_Type;

typedef struct {
 u32 DIN;
 u32 dummy1;
 u32 dummy2;
 u32 DOUT;
} ALU_DOUBLE_Type;

#define UART0  ((UART_Type*) XPAR_UARTLITE_0_BASEADDR)
#define ALU_DOUBLE ((ALU_DOUBLE_Type*) XPAR_ALU_DOUBLE_0_S00_AXI_BASEADDR)

構造体の変数は上記のような書き方をすると上から下に向かって変数がメモリに割り当てられます。構造体のポインタ定義をそれぞれのペリフェラルのベースアドレスに割り当てると構造体の変数への読み書きがそのまま、ペリフェラルのインターフェースへの読み書きになります。

あと忘れてはいけないのが乱数生成関数 getrnd() の移植。rand() 関数を使って対応しました。Linux 版がこの実装ですね。

// Return random number
short getrnd(short value) {
    return(rand() % value) + 1;
}

あともうひと手間でとりあえずの移植は完了です。
basic.c の中に putnum() という関数が定義されているのですが、これがすでに存在する関数なので、別な名前にします。tiny_putnum() という名前で置換しておきます。

これで BASIC が走るようになっているはずです。Arty を USB で接続して、Bitstream ファイルを書き込んで、Microblaze のコードを転送しましょう。Program FPGA アイコンをクリックして、Program ボタンをクリックします。Hardware Platform に system_wrapper_hw_platform_1 が選択されていることを確認のこと。



これで Arty に回路が書き込まれました。
最後に BASIC のコードを転送する前に、ターミナルソフトを起動して、シリアル接続をしておきます。9600bps で 8bit パリティ無です。ターミナルソフトの準備が終わったら、Run System Debugger (緑の丸に白三角)から、Run As/Launch on Hardware を選択しましょう。


プログラムが転送されて、Microblaze edition の豊四季タイニーBASIC が立ち上がりましたか?


成功すれば上記のような画面が出るはずです。やったー!

ではいよいよカスタムペリフェラルを BASIC から呼び出せるようにします。DOUBLE() という関数を作成してみましょう。引数に数字が与えられるとその二倍の値を返すようにします。実装には同様に引数を与えて結果が返る RND() 関数を参考にします。

まずは DOUBLE() 関数の本体から。alu_double にアクセスして演算結果を返す関数を作ります。getX2() という32bitの値を受け取って結果を返す関数です。
slv_reg0 の書き込みアドレスには DIN という 32bit の変数をアサインしたので、ALU_DOUBLE->DIN に値を代入することで、slv_reg0 へのデータ書き込みが実行されます。また、slv_reg3 のアドレスには DOUT という変数をアサインしたので、ALU_DOUBLE->DOUT を読み出せば演算結果を得られます。簡単ですねー。

short getX2(short value) {
    ALU_DOUBLE->DIN = value;
    return ALU_DOUBLE->DOUT;
}

あとはこれを呼び出す DOUBLE() 関数の実装です。まずは定義。コマンド名を kwtbl に追加して、i-code を列挙型に追加、後ろに空白を入れない中間コード i_nsa[] にそれぞれ追加します。


// Keyword table
const char *kwtbl[] = {
    "GOTO", "GOSUB", "RETURN",
    "FOR", "TO", "STEP", "NEXT",
    "IF", "REM", "STOP",
    "INPUT", "PRINT", "LET",
    ",", ";",
    "-", "+", "*", "/", "(", ")",
    ">=", "#", ">", "=", "<=", "<",
    "@", "RND", "DOUBLE", "ABS", "SIZE",
    "LIST", "RUN", "NEW"
};

// Keyword count
#define SIZE_KWTBL (sizeof(kwtbl) / sizeof(const char*))

// i-code(Intermediate code) assignment
enum {
    I_GOTO, I_GOSUB, I_RETURN,
    I_FOR, I_TO, I_STEP, I_NEXT,
    I_IF, I_REM, I_STOP,
    I_INPUT, I_PRINT, I_LET,
    I_COMMA, I_SEMI,
    I_MINUS, I_PLUS, I_MUL, I_DIV, I_OPEN, I_CLOSE,
    I_GTE, I_SHARP, I_GT, I_EQ, I_LTE, I_LT,
    I_ARRAY, I_RND, I_DOUBLE, I_ABS, I_SIZE,
    I_LIST, I_RUN, I_NEW,
    I_NUM, I_VAR, I_STR,
    I_EOL
};

// List formatting condition
// 後ろに空白を入れない中間コード
const unsigned char i_nsa[] = {
    I_RETURN, I_STOP, I_COMMA,
    I_MINUS, I_PLUS, I_MUL, I_DIV, I_OPEN, I_CLOSE,
    I_GTE, I_SHARP, I_GT, I_EQ, I_LTE, I_LT,
    I_ARRAY, I_RND, I_DOUBLE, I_ABS, I_SIZE
};

最後に中間コードを実行する関数 ivalue() に DOUBLE() を実装すれば出来上がりです。I_RND の隣にでも加えておきましょうか。

case I_DOUBLE:                 //関数DOUBLEの場合
    cip++;                             //中間コードポインタを次へ進める
    value = getparam();     //括弧の値を取得
    if (err)                            //もしエラーが生じたら
        break;                        //ここで打ち切る
    value = getX2(value); // x2 の計算結果を取得
    break;                            //ここで打ち切る

さぁ、もう一度プログラムを転送しなおして実行してみましょう。

うまくいったようです!ちゃんと演算結果を返してくれていますね。

ここまで出来ると、いろいろ夢は膨らみますね。とりあえず次は割り込み付きのペリフェラルを実装してみたいところです。ディスプレイを駆動する回路やら、キーボード入力を受け付ける回路を書いてみるとそれもまたいいですよねー。他には SDK プロジェクトを SPI flash に書き込んで毎回電源を入れるだけでブート出来るようにもしてみたいです。




0 件のコメント: