- SUZUKI PLAN - Video Game System - Central Processing Unit
- VGS-CPU は 8bit CPU 並に フルアセンブリ言語でのコーディングがし易い 32bit の CISC仮想CPU です
- 本リポジトリでは VGS-CPU モジュールに加え, アセンブラ
(vgsasm)
や デバッガ(vgsdrun)
など, VGS-CPU向けアプリケーションの開発に必要なプログラミングツール(ミドルウェア)も提供しています
make test
を実行して全ての項目が success になる環境であれば動作できます- big-endian の環境には対応していません
UNIX系OS全般(mac/Linuxを含む) の ターミナル で 以下のコマンドを実行してください
git clone https://github.com/suzukiplan/vgs-cpu.git
cd vgs-cpu
make build
上記を実行すればカレントディレクトリに以下のファイルが生成されます
フィアル名 | 概要 |
---|---|
vgscpu.a |
VGS-CPU API のアーカイブライブラリ |
vgsasm |
アセンブラ (実行モジュール) |
vgsdrun |
デバッガ (実行モジュール) |
※Windowsでも使えますがWindowsの場合はmakefileの内容を解析して自力で何とかしてください
- UNIX系OS全般(mac/Linuxを含む) の ターミナル で
make
を実行すれば, できることが書いてあります make build
を実行すればアセンブラ (vgsasmコマンド) をビルドできます- 利用したいプログラムに
git subversion add
して利用する想定です - 利用したいプロジェクトの makefile で適宜ビルドしてご利用ください
- コンパイル時に必要なヘッダファイル: vgscpu.h
- リンク時に必要なアーカイブ:
vgscpu.a
- 依存ライブラリ: 標準Cランタイムのみ
void *vgscpu_create_context();
void *vgscpu_create_specific_context(unsigned int ps, unsigned int ss, unsigned int ms);
- CPUコンテキストを作成
vgscpu_create_context
はvgscpu_create_specific_context(64KB - 4byte, 256KB, 4MB)
と等価ps
: プログラムサイズの上限(byte)ss
: スタックサイズの上限(byte)ms
: メモリサイズの上限(byte)
void vgscpu_regist_interrupt(void *ctx, unsigned char n, int (*interrupt)(struct vgscpu_context *));
INT
命令で実行される 割り込み番号n の 関数を登録する- 割り込み関数 VGS-CPU を利用するコンピュータシステム上 で コンテキスト毎 に 最大256個 登録することができる
- Windows で利用する場合, 割り込み関数 の 呼び出し規約 が
__cdecl
(C規約) である点に注意すること
例: HELLO, VGS-CPU
と標準出力する割り込み (番号0) を登録
int hello_world(struct vgscpu_context *c)
{
printf("HELLO, VGS-CPU");
return 0;
}
int main()
{
struct vgscpu_context *c = (struct vgscpu_context *)vgscpu_create_context();
vgscpu_regist_interrupt(c, 0, hello_world);
:
int vgscpu_load_program(void *ctx, void *pg, size_t size);
- CPUコンテキスト に プログラム を ロード
- 成功すると 0 を返し, 失敗すると -1 を返す
int vgscpu_run(void *ctx);
- プログラムを実行
- program counter と stack pointer はリセットされる
BRK
命令を検出すると 0 を返す- 例外を検出すると -1 を返しエラー情報が格納される
const char* vgscpu_get_last_error(void *ctx);
- 最後に発生したエラー情報 (
\0
で終端するASCIIテキスト) を返す
void vgscpu_release_context(void *ctx);
- CPUコンテキストを開放
type | name | size | outline |
---|---|---|---|
register | r.a | 32bit | general register (accumulater) |
register | r.b | 32bit | general register (base) |
register | r.c | 32bit | general register (counter) |
register | r.d | 32bit | general register (data) |
register | r.s | 32bit | stack pointer |
register | r.p | 32bit | program counter |
status flag | f.z | 32bit | zero flag |
status flag | f.q | 32bit | equal flag (0: euqal, -1: negative, 1:positive) |
memory | m | variable | main memory |
memory | s | variable | stack memory |
memory | p | variable | program context |
- 一般的なCPUと異なる点として以下のような特徴があります
- フラグは32bitで管理(この方がトータルの演算コストが低くなる)
- 主記憶を memory / stack / program で分離している
- コンテキスト確保時は全ての領域がゼロクリアされる
vgscpu_run
呼び出し時はr.s
とr.p
が常にリセットされる- スタック領域(s)の内容は次回ループ時に引き継がれない
- 次回ループに残すべき情報は主記憶(m)をストアする必要がある
GR
: general registerA
: 32bitAH
: 16bit (A and 0xffff)AO
: 8bit (A and 0xff)- B, C, D についても同様
n
: literal number255
: decimal number$FF
: hexadecimal number0377
: octalB11111111
: binary
[n]
: address[n]
: 通常, アドレスが指す 32bit の領域を意味する[n]H
: 通常, アドレスが指す 16bit の領域を意味する[n]O
: 通常, アドレスが指す 8bit の領域を意味する- 例外的にアドレスの先頭を単純に指し示す場合は単に
[n]
と表記
[GR]
: general register address[GR]
: 通常, アドレス (GR値) が指す 32bit の領域を意味する[GR]H
: 通常, アドレス (GR値) が指す 16bit の領域を意味する[GR]O
: 通常, アドレス (GR値) が指す 8bit の領域を意味する- 例外的にアドレスの先頭を単純に指し示す場合は単に
[GR]
と表記
operand | z | q | outline |
---|---|---|---|
PUSH GR |
- | - | 汎用レジスタ の 値 を スタック領域 に積む |
POP GR |
- | - | スタック領域(s) の 内容 を 汎用レジスタ に戻す |
LD GR, n |
- | - | 汎用レジスタ <- 定数 |
LD GR, [n] |
- | - | 汎用レジスタ <- 主記憶のn番地の内容 |
LD GR1, GR2 |
- | - | 汎用レジスタ1 に 汎用レジスタ2 の内容をロード |
LD GR1, [GR2] |
- | - | 汎用レジスタ1 に 主記憶の 汎用レジスタ2 が指す番地の内容をロード |
ST GR, [n] |
- | - | 汎用レジスタ の 値 -> 主記憶のn番地 |
ST GR, [GR2] |
- | - | 汎用レジスタ の 値 -> 主記憶の 汎用レジスタ2 が指す番地 |
operand | z | q | outline |
---|---|---|---|
ADD GR, n |
o | - | 汎用レジスタ <- 汎用レジスタ + 定数 |
ADD GR, [n] |
o | - | 汎用レジスタ <- 汎用レジスタ + 主記憶のn番地の内容 |
ADD GR1, GR2 |
o | - | 汎用レジスタ1 <- 汎用レジスタ1 + 汎用レジスタ2 |
SUB GR, n |
o | - | 汎用レジスタ <- 汎用レジスタ - 定数 |
SUB GR, [n] |
o | - | 汎用レジスタ <- 汎用レジスタ - 主記憶のn番地の内容 |
SUB GR1, GR2 |
o | - | 汎用レジスタ1 <- 汎用レジスタ1 - 汎用レジスタ2 |
INC GR |
o | - | 汎用レジスタ <- 汎用レジスタ + 1 |
DEC GR |
o | - | 汎用レジスタ <- 汎用レジスタ - 1 |
MUL GR, n |
o | - | 汎用レジスタ <- 汎用レジスタ × 定数 |
MUL GR, [n] |
o | - | 汎用レジスタ <- 汎用レジスタ × 主記憶のn番地の内容 |
MUL GR1, GR2 |
o | - | 汎用レジスタ1 <- 汎用レジスタ1 × 汎用レジスタ2 |
DIV GR, n |
o | - | 汎用レジスタ <- 汎用レジスタ ÷ 定数 |
DIV GR, [n] |
o | - | 汎用レジスタ <- 汎用レジスタ ÷ 主記憶のn番地の内容 |
DIV GR1, GR2 |
o | - | 汎用レジスタ1 <- 汎用レジスタ1 ÷ 汎用レジスタ2 |
MOD GR, n |
o | - | 汎用レジスタ <- 汎用レジスタ mod 定数 |
MOD GR, [n] |
o | - | 汎用レジスタ <- 汎用レジスタ mod 主記憶のn番地の内容 |
MOD GR1, GR2 |
o | - | 汎用レジスタ1 <- 汎用レジスタ1 mod 汎用レジスタ2 |
- ADD, SUB, MUL, DIV, MOD の場合 GR, GR1 は A を使うと高速になる
- todo: 補数演算が追加で必要かもしれない
operand | z | q | outline |
---|---|---|---|
AND GR, n |
o | - | 汎用レジスタ <- 汎用レジスタ & 定数 |
AND GR, [n] |
o | - | 汎用レジスタ <- 汎用レジスタ & 主記憶のn番地の内容 |
AND GR1, GR2 |
o | - | 汎用レジスタ1 <- 汎用レジスタ1 & 汎用レジスタ2 |
OR GR, n |
o | - | 汎用レジスタ <- 汎用レジスタ ` |
OR GR, [n] |
o | - | 汎用レジスタ <- 汎用レジスタ ` |
OR GR1, GR2 |
o | - | 汎用レジスタ1 <- 汎用レジスタ1 ` |
XOR GR, n |
o | - | 汎用レジスタ <- 汎用レジスタ ^ 定数 |
XOR GR, [n] |
o | - | 汎用レジスタ <- 汎用レジスタ ^ 主記憶のn番地の内容 |
XOR GR1, GR2 |
o | - | 汎用レジスタ1 <- 汎用レジスタ1 ^ 汎用レジスタ2 |
NOT GR |
o | - | 汎用レジスタ <- not 汎用レジスタ |
SHL GR, n |
o | - | 汎用レジスタ を n bit 左シフト (0 <= n < 256) |
SHR GR, n |
o | - | 汎用レジスタ を n bit 右シフト (0 <= n < 256) |
- AND, OR, XOR の場合 GR, GR1 は A を使うと高速になる
- 2値を比較して
f.q
を更新 - 左辺
>
右辺 :f.q
=1
- 左辺
=
右辺 :f.q
=0
- 左辺
<
右辺 :f.q
=-1
CMP
: 符号なしで比較CMP2
: 符号あり(2の補数)で比較
operand | z | q | outline |
---|---|---|---|
CMP GR, n |
- | o | 汎用レジスタ : 定数 |
CMP GR, [n] |
- | o | 汎用レジスタ : 主記憶のn番地の内容 |
CMP GR1, GR2 |
- | o | 汎用レジスタ1 : 汎用レジスタ2 |
CMP2 GR, n |
- | o | 汎用レジスタ : 定数 |
CMP2 GR, [n] |
- | o | 汎用レジスタ : 主記憶のn番地の内容 |
CMP2 GR1, GR2 |
- | o | 汎用レジスタ1 : 汎用レジスタ2 |
operand | z | q | outline |
---|---|---|---|
JMP n |
- | - | アドレスn へ無条件ジャンプ |
JZ n |
x | - | f.z が 1 の場合, アドレスn へジャンプ |
JNZ n |
x | - | f.z が 0 の場合, アドレスn へジャンプ |
JE n |
- | x | f.q が 0 の場合, アドレスn へジャンプ |
JNE n |
- | x | f.q が 非0 の場合, アドレスn へジャンプ |
JN n |
- | x | f.q が -1 の場合, アドレスn へジャンプ |
JNN n |
- | x | f.q が 非-1 の場合, アドレスn へジャンプ |
JP n |
- | x | f.q が 1 の場合, アドレスn へジャンプ |
JNP n |
- | x | f.q が 非1 の場合, アドレスn へジャンプ |
CAL n |
- | - | アドレスn を呼び出す |
RET |
- | - | CAL の呼び出し元へ復帰 |
BRK |
- | - | プログラムの実行状態を解除 |
INT n |
? |
? |
割り込み番号n を実行 |
CAL
を実行すると, スタック領域に 戻りアドレス(4byte) を PUSH して アドレスn へジャンプするRET
を実行すると, スタック領域から 戻りアドレス(4byte) を POP して 戻りアドレス へジャンプする- アプリケーションにより, 誤って 戻りアドレス が
POP
されると stack underflow または 不正アドレス へジャンプする恐れがある INT
で 呼び出す割り込み はvgscpu_regist_interrupt
で予め登録してなければならないINT
で 登録した割り込み関数 の 戻り値 (int型) は レジスタD にセットされるINT
の 戻り値以外 の 呼び出し規約の取り決め は 割り込みの実装者 に委ねられるが, 原則以下の割当を推奨する- レジスタA: 算術演算の類のパラメタ (accumulater)
- レジスタB: 主記憶アドレスの起点 (base)
- レジスタC: 個数 (counter)
$ vgsasm [-o output-binary] input-source
C/C++言語形式 の コメント に 対応
PUSH A /* C形式コメント (複数行可能) */
POP A // C++形式コメント
- 定数の表記方法は 2進数, 8進数, 10進数, 16進数 の 4種類 を サポート
- 負数表記 は 10進数 の 場合 にのみできる
LD A, B10101110 // 2進数 (頭に B を付ける)
LD B, 0765 // 8進数 (頭に 0 を付ける)
LD C, 12345678 // 10進数 (頭に - または 1〜9 を付ける)
LD D, $deadbeef // 16進数 (頭に $ を付ける)
主記憶のアドレス表記には [literal]
を用いる
ST A, [$123] // アドレス $123 から 4byte (32bit) の内容を参照
ST B, [$123]H // アドレス $123 から 2byte (16bit) の内容を参照
ST C, [$123]O // アドレス $123 から 1byte (8bit) の内容を参照
- サフィックス
:
を付けたものがラベルとなる - ラベルは 1バイト以上 255バイト以下 の 1トークン文字列 とする
- ブランチ命令の飛び先には必ずラベルを用いる (アドレス値の直指定はできない)
CMP A, B
JE equal // A = B
JP positive // A > B
JN negative // A < B
equal:
LD C, 0
BRK
positive:
LD C, 1
BRK
negative:
LD C, -1
BRK
Operands を 参照
$ vgsdrun [-s] input-source
- アセンブリ言語ソース (.asm) ファイルを実際に vgs-cpu で実行した時の流れを表示
-s
オプションを指定すればステップ実行
<address>: opcodes : line#<number> > assembly-source
$ ./vgsdrun src/test/asm_add_a.asm
assembling: src/test/asm_add_a.asm on memory
starting debug run.
00000000: ----- start -----
00000000: 08 E8 03 : line#00006 > LD A, 1000
00000003: 5D 00 : line#00007 > ADD A, 0
00000005: A6 E8 03 : line#00008 > CMP A, 1000
00000008: F8 02 01 00 00 : line#00009 > JNE $102 (test-failed)
0000000d: F5 02 01 00 00 : line#00010 > JZ $102 (test-failed)
00000012: 5D FF : line#00011 > ADD A, 255
00000014: A6 E7 04 : line#00012 > CMP A, 1255
00000017: F8 02 01 00 00 : line#00013 > JNE $102 (test-failed)
0000001c: F5 02 01 00 00 : line#00014 > JZ $102 (test-failed)
00000021: 5E 00 01 : line#00015 > ADD A, 256
00000024: A6 E7 05 : line#00016 > CMP A, 1511
00000027: F8 02 01 00 00 : line#00017 > JNE $102 (test-failed)
0000002c: F5 02 01 00 00 : line#00018 > JZ $102 (test-failed)
00000031: 5E FF FF : line#00019 > ADD A, 65535
00000034: A7 E6 05 01 00 : line#00020 > CMP A, 67046
00000039: F8 02 01 00 00 : line#00021 > JNE $102 (test-failed)
0000003e: F5 02 01 00 00 : line#00022 > JZ $102 (test-failed)
00000043: 5F 00 00 01 00 : line#00023 > ADD A, 65536
00000048: A7 E6 05 02 00 : line#00024 > CMP A, 132582
0000004d: F8 02 01 00 00 : line#00025 > JNE $102 (test-failed)
00000052: F5 02 01 00 00 : line#00026 > JZ $102 (test-failed)
00000057: 5F 1A FA FD FF : line#00027 > ADD A, -132582
0000005c: F6 02 01 00 00 : line#00028 > JNZ $102 (test-failed)
00000061: 09 78 56 34 12 : line#00031 > LD A, $12345678
00000066: 12 00 00 00 00 : line#00032 > ST A, [0]
0000006b: 07 00 : line#00033 > LD A, 0
0000006d: 63 00 00 00 00 : line#00034 > ADD A, [0]O
00000072: A5 78 : line#00035 > CMP A, $78
00000074: F8 02 01 00 00 : line#00036 > JNE $102 (test-failed)
00000079: F5 02 01 00 00 : line#00037 > JZ $102 (test-failed)
0000007e: 5F 88 FF FF FF : line#00038 > ADD A, -120
00000083: F6 02 01 00 00 : line#00039 > JNZ $102 (test-failed)
00000088: 64 00 00 00 00 : line#00040 > ADD A, [0]H
0000008d: A6 78 56 : line#00041 > CMP A, $5678
00000090: F8 02 01 00 00 : line#00042 > JNE $102 (test-failed)
00000095: F5 02 01 00 00 : line#00043 > JZ $102 (test-failed)
0000009a: 5F 88 A9 FF FF : line#00044 > ADD A, -22136
0000009f: F6 02 01 00 00 : line#00045 > JNZ $102 (test-failed)
000000a4: 65 00 00 00 00 : line#00046 > ADD A, [0]
000000a9: A7 78 56 34 12 : line#00047 > CMP A, $12345678
000000ae: F8 02 01 00 00 : line#00048 > JNE $102 (test-failed)
000000b3: F5 02 01 00 00 : line#00049 > JZ $102 (test-failed)
000000b8: 5F 88 A9 CB ED : line#00050 > ADD A, -305419896
000000bd: F6 02 01 00 00 : line#00051 > JNZ $102 (test-failed)
000000c2: 1E 00 00 11 11 : line#00054 > LD B, $11110000
000000c7: 32 11 11 : line#00055 > LD C, $00001111
000000ca: 48 11 11 11 11 : line#00056 > LD D, $11111111
000000cf: 60 : line#00057 > ADD A, B
000000d0: A7 00 00 11 11 : line#00058 > CMP A, $11110000
000000d5: F8 02 01 00 00 : line#00059 > JNE $102 (test-failed)
000000da: F5 02 01 00 00 : line#00060 > JZ $102 (test-failed)
000000df: 61 : line#00061 > ADD A, C
000000e0: A7 11 11 11 11 : line#00062 > CMP A, $11111111
000000e5: F8 02 01 00 00 : line#00063 > JNE $102 (test-failed)
000000ea: F5 02 01 00 00 : line#00064 > JZ $102 (test-failed)
000000ef: 62 : line#00065 > ADD A, D
000000f0: A7 22 22 22 22 : line#00066 > CMP A, $22222222
000000f5: F8 02 01 00 00 : line#00067 > JNE $102 (test-failed)
000000fa: F5 02 01 00 00 : line#00068 > JZ $102 (test-failed)
000000ff: 46 01 : line#00070 > LD D, 1
00000101: 00 : line#00071 > BRK
registers:
c->r.a = 22222222, c->r.b = 11110000, c->r.c = 00001111, c->r.d = 00000001
c->r.p = 00000101, c->r.s = 00000000, c->f.z = 00000000, c->f.q = 00000000
$
$ ./vgsdrun src/test/asm_mul.asm -s
assembling: src/test/asm_mul.asm on memory
starting debug run.
00000000: ----- start-a -----
00000000: 08 E8 03 : line#00006 > LD A, 1000
00000003: 6F 64 : line#00007 > MUL A, 100
00000005: A7 A0 86 01 00 : line#00008 > CMP A, 100000
0000000a: F8 08 03 00 00 : line#00009 > JNE $308 (test-failed)
> h
command usage:
- h ... help
- r ... show registers
- q ... quit
- other ... execute next line
> r
registers:
c->r.a = 000186A0, c->r.b = 00000000, c->r.c = 00000000, c->r.d = 00000000
c->r.p = 0000000A, c->r.s = 00000000, c->f.z = 00000000, c->f.q = 00000000
> q
$