こんにちはマイコン制御

May FORTH be with you.

ジェダイへの道 Retroのソースを読んでみる2

今日は雨の中、熊本駅前に行って黒竜紅でラーメンを食べて、図書館で本を借りて、川尻を散歩してきました。川尻は職人の町、歴史の町ということで今も佇まいがよろしい感じでした。工芸館のはなれとつながっていた瑞鷹の展示スペースも面白かった。

借りてきた本は3つ。 

熱血!アセンブラ入門

熱血!アセンブラ入門

 

 

Head First C ―頭とからだで覚えるCの基本

Head First C ―頭とからだで覚えるCの基本

 

 

コーディングを支える技術 ~成り立ちから学ぶプログラミング作法 (WEB+DB PRESS plus)

コーディングを支える技術 ~成り立ちから学ぶプログラミング作法 (WEB+DB PRESS plus)

 

 

レトロのソースはまずMbed用のVMのコア中のコア、インプットもアウトプットもないという状態にまで、他もところどころいじりつつ切り詰めてみることにしました。

/* Ngaro VM ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
   Copyright (c) 2008 - 2012, Charles Childers
   Copyright (c) 2009 - 2010, Luke Parrish
   Copyright (c) 2010,        Marc Simpson
   Copyright (c) 2010,        Jay Skeer
   Copyright (c) 2011,        Kenneth Keating
   Copyright (c) 2011,        Erturk Kocalar
   ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 
   degraded by Senji Niban
*/

#include <stdio.h>
#include <string.h>

#define CELL              int
#define IMAGE_SIZE       1024
#define ADDRESSES         128
#define STACK_DEPTH        32
#define PORTS              16

CELL sp, rsp, ip;
CELL data[STACK_DEPTH];
CELL address[ADDRESSES];
CELL ports[PORTS];
CELL image[IMAGE_SIZE];

/* Macros ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */
#define DROP data[sp] = 0; if (--sp < 0) ip = IMAGE_SIZE;
#define TOS  data[sp]
#define NOS  data[sp-1]
#define TORS address[rsp]

enum opcode {VM_NOP, VM_LIT, VM_DUP, VM_DROP, VM_SWAP, VM_PUSH, VM_POP,
             VM_LOOP, VM_JUMP, VM_RETURN, VM_GT_JUMP, VM_LT_JUMP,
             VM_NE_JUMP,VM_EQ_JUMP, VM_FETCH, VM_STORE, VM_ADD,
             VM_SUB, VM_MUL, VM_DIVMOD, VM_AND, VM_OR, VM_XOR, VM_SHL,
             VM_SHR, VM_ZERO_EXIT, VM_INC, VM_DEC, VM_IN, VM_OUT,
             VM_WAIT };

/* The VM ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */
void init_vm() {
   CELL a;
   ip = 0;  sp = 0;  rsp = 0;
   for (a = 0; a < STACK_DEPTH; a++)
      data[a] = 0;
   for (a = 0; a < ADDRESSES; a++)
      address[a] = 0;
   for (a = 0; a < IMAGE_SIZE; a++)
      image[a] = 0;
   for (a = 0; a < PORTS; a++)
      ports[a] = 0;
}

void process() {
  CELL a, b, opcode;
  opcode = image[ip];

  switch(opcode) {
    case VM_NOP:
         break;
    case VM_LIT:
         sp++;
         ip++;
         TOS = image[ip];
         break;
    case VM_DUP:
         sp++;
         data[sp] = NOS;
         break;
    case VM_DROP:
         DROP
         break;
    case VM_SWAP:
         a = TOS;
         TOS = NOS;
         NOS = a;
         break;
    case VM_PUSH:
         rsp++;
         TORS = TOS;
         DROP
         break;
    case VM_POP:
         sp++;
         TOS = TORS;
         rsp--;
         break;
    case VM_LOOP:
         TOS--;
         if (TOS != 0 && TOS > -1)
         {
           ip++;
           ip = image[ip] - 1;
         }
         else
         {
           ip++;
           DROP;
         }
         break;
    case VM_JUMP:
         ip++;
         ip = image[ip] - 1;
         if (ip < 0)
           ip = IMAGE_SIZE;
         else {
           if (image[ip+1] == 0)
             ip++;
           if (image[ip+1] == 0)
             ip++;
         }
         break;
    case VM_RETURN:
         ip = TORS;
         rsp--;
         if (ip < 0)
           ip = IMAGE_SIZE;
         else {
           if (image[ip+1] == 0)
             ip++;
           if (image[ip+1] == 0)
             ip++;
         }
         break;
    case VM_GT_JUMP:
         ip++;
         if(NOS > TOS)
           ip = image[ip] - 1;
         DROP DROP
         break;
    case VM_LT_JUMP:
         ip++;
         if(NOS < TOS)
           ip = image[ip] - 1;
         DROP DROP
         break;
    case VM_NE_JUMP:
         ip++;
         if(TOS != NOS)
           ip = image[ip] - 1;
         DROP DROP
         break;
    case VM_EQ_JUMP:
         ip++;
         if(TOS == NOS)
           ip = image[ip] - 1;
         DROP DROP
         break;
    case VM_FETCH:
         TOS = image[TOS];
         break;
    case VM_STORE:
         image[TOS] = NOS;
         DROP DROP
         break;
    case VM_ADD:
         NOS += TOS;
         DROP
         break;
    case VM_SUB:
         NOS -= TOS;
         DROP
         break;
    case VM_MUL:
         NOS *= TOS;
         DROP
         break;
    case VM_DIVMOD:
         a = TOS;
         b = NOS;
         TOS = b / a;
         NOS = b % a;
         break;
    case VM_AND:
         a = TOS;
         b = NOS;
         DROP
         TOS = a & b;
         break;
    case VM_OR:
         a = TOS;
         b = NOS;
         DROP
         TOS = a | b;
         break;
    case VM_XOR:
         a = TOS;
         b = NOS;
         DROP
         TOS = a ^ b;
         break;
    case VM_SHL:
         a = TOS;
         b = NOS;
         DROP
         TOS = b << a;
         break;
    case VM_SHR:
         a = TOS;
         b = NOS;
         DROP
         TOS = b >>= a;
         break;
    case VM_ZERO_EXIT:
         if (TOS == 0) {
           DROP
           ip = TORS;
           rsp--;
         }
         break;
    case VM_INC:
         TOS += 1;
         break;
    case VM_DEC:
         TOS -= 1;
         break;
    case VM_IN:
         a = TOS;
         TOS = ports[a];
         ports[a] = 0;
         break;
    case VM_OUT:
         ports[0] = 0;
         ports[TOS] = NOS;
         DROP DROP
         break;
    case VM_WAIT:
         //handle_devices();
         break;
    default:
         rsp++;
         TORS = ip;
         ip = image[ip] - 1;

         if (ip < 0)
           ip = IMAGE_SIZE;
         else {
           if (image[ip+1] == 0)
             ip++;
           if (image[ip+1] == 0)
             ip++;
         }
         break;
  }
}

/* Main ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */
int main(int argc, char **argv)
{
	int a = 1;
	while (a) {
      init_vm();
    for (ip = 0; ip < IMAGE_SIZE; ip++){
      process();
    }
	  a = 0;
	}
	return 0;
}

 

取り敢えず、コンパイルしたりprintfデバッグしたりして、間違いは無さそう、といったところで改めてこちらのサイトを教科書にしつつソースを読み進めてみます。

まずはスタックは通常のスタックがdata[ ]、リターンスタックがadress[ ]、という配列で実装されていて、image[ ]はブート時に読み出されるROM?イメージ相当のもの。今現在ROMの中身は空っぽ。この3つのポインタにあたるのがそれぞれsp、rsp、ipという変数になり、sp、rspは小さい数から順番にスタックされていきます。最大値が一番後に積まれたスタックのポインタを指しています。port[ ]はこのVMの入出力用の配列ですが、今は何も繋がっていない状態です。

これらの配列の中身は、関数init_vm()が呼び出された時に全て初期化され値が”0”になります。

つぎがマクロで、実際の処理としてのDROPと、TOS= Top of Stack、NOS= Next of Stack、TORS= Top of Return Stackを定義した後に、VMのオペコード31個を定義しています。VM_WAITはVMを動かす環境によって変わってくるので、今はコメントアウトしました。

最後にメインで、今回は、まず状態の初期化を行いイメージを頭から一つづつ実行して終わる、という形にしました。今はイメージがすべて”0”なのでVM_NOPがイメージサイズ分実行されて終了します。

まだ何も出来ない状態のVMではありますが、何かスッキリしていて不安になるくらい簡単です。Verilogで作ったスタックベースのCPUが200行で書けてしまうそうなので良いのかもしれませんが。

 

次の段階としては、初期化の後にイメージにアセンブラのソースを1つずつimage[ ]に入れてあげればそれが実行されるはずです。元のmbedのソースではファイルシステムによってイメージを読み込みますが、この方式であればファイルシステムを持たない環境でもブートが可能です。

取り敢えずソースを改造しつつ読んでいますが、この後は上の方法でオペコードの中身を調べながら今度はブートイメージを読み進める感じですね。

 

あまり意味は無いと思いますが、デバッグ用のprintfを入れている版をシェアしてみます。