ジェダイへの道 Retroのソースを読んでみる2
今日は雨の中、熊本駅前に行って黒竜紅でラーメンを食べて、図書館で本を借りて、川尻を散歩してきました。川尻は職人の町、歴史の町ということで今も佇まいがよろしい感じでした。工芸館のはなれとつながっていた瑞鷹の展示スペースも面白かった。
借りてきた本は3つ。
- 作者: David Griffiths,Dawn Griffiths,中田秀基(監訳),木下哲也
- 出版社/メーカー: オライリージャパン
- 発売日: 2013/04/03
- メディア: 大型本
- この商品を含むブログ (5件) を見る
コーディングを支える技術 ~成り立ちから学ぶプログラミング作法 (WEB+DB PRESS plus)
- 作者: 西尾泰和
- 出版社/メーカー: 技術評論社
- 発売日: 2013/04/24
- メディア: 単行本(ソフトカバー)
- この商品を含むブログ (33件) を見る
レトロのソースはまず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を入れている版をシェアしてみます。