公開日:2026/04/11
title.png

38th Season


#38-10 週刊「スタックマシンを作る」第二号

 新たに、スタックマシンと呼ばれる方式の、新規CPUコアを設計している所です。
 本HPにて、Cmod A7ボード(FPGA)を使って開発しているオリジナル16ビットCPUコアMoscoviumは汎用レジスタを使って処理を進めるCPUなので、汎用レジスタマシンと呼ばれます。スタックマシンは汎用レジスタマシンとは異なる考え方に基づくCPU方式です。

 前回命名したSamariumコアに関して、新規CPUコアの設計に於いて一番楽しいところ、命令セットアーキテクチャを考えて行きましょう。

命令セットアーキテクチャ

 Samariumコアの命令セットアーキテクチャ(ISA;Instruction Set Architecture)を検討して行きたいと思います。

命令コード

 スタックマシンは、スタックからデータを取り出して、演算などを実行し、結果をスタックへ積む、という手順が基本動作となっています。従って、スタックから取り出す(pop)データ数と、オペランドの種類(符号化即値または即値)によって以下の命令コードに分類します。

bit7 bit6 bit5 bit4 bit3 bit2 bit1 bit0 Instruction
0 0 0 INST_0 other N pop instruction
0 0 1 INST_1 1 pop instruction
0 1 0 INST_2 2 pop instruction
0 1 1 EIMM5 Encoded immediate instruction
1 IMM7 Immediate instruction

 スタックマシンは処理の対象となるレジスタを指定する必要が無いので、上表の様に僅か8ビット幅の命令コードで充分足りてしまう事は、前回解説済みです。

命令セット

 各命令の詳細動作に関しては、こちらの命令セットマニュアルを参照してください。

 以下に、全30命令を処理内容別に分類しておきます。

※丸カッコ()内は、命令コードが等しい別名です。

 なお、充分に精査して、実用可能な範囲内(※)で極力命令数は絞ってあります。このCPUは論理規模縮小が第一義なので。そうは言えども、実用性を犠牲にした、おもちゃ🪀みたいなCPUなら、いくらでも小さくできますからねぇ。おもちゃみたいなCPUを作るつもりは毛頭なくて、あくまで実用的なCPUコアの設計が目的です。

※「実用可能な範囲」: 少なくとも、16ビットシフト乗除算ルーチンや、32ビット加減算ルーチンを記述可能な事は、実際にプログラミングして確認済み。また、割り込み機能や、外部コプロセッサI/Fによる拡張性も保有。

定数の表現

 8ビット命令コードに許される即値のビット幅は多くありません。そこで、5ビット符号化即値を取るpsei命令と、7ビット即値を取るsldi命令の2種類を用意して、定数値をスタックへ積む機能を拡充してあります。
 即値は、指定された数値その物、をデータとして使用します。符号化即値は、使用頻度の高い数値が符号化されていて、より短い命令コードで16ビット定数を指定可能となる事が特長です。

 16ビットの定数をスタックへ積もうとしても、即値命令は7ビット即値しか指定できません。以下に16ビット定数をスタックへ積む命令列の例を示します。

  例)16ビット定数0x8765をスタックへ積む。

 プログラム中、分岐先のアドレス指定や、演算に使う定数、変数の格納アドレス指定等、16ビット定数を使用する頻度は高いです。16ビット定数の指定に毎回3命令を要していては、コード効率と実行速度の低下が懸念されます。この懸念を払しょくするため、以下の対策を実施した結果の命令セットとなっています。

 上表の通り、符号化即値命令には単なる定数の他にも、スタックポインタ値(STK_PTR)、プログラムカウンタ値(PRG_CNT)、割り込みベクタ値(INT_VEC)等、CPUの内部レジスタを参照する機能も割り当てられています。

 ここで、CPU状態レジスタCPU_STSは、CPU外部から受け取る16ビット幅の信号です。CPU最上位モジュールの入力信号その物の値なので、CPU_STSへのデータ割り当ては実装依存です。

※CORE_ID=0x0620(BCD)は、元素Sm(サマリウム)の原子番号62を表しています。将来、CPU種を判定するための予約機能です。

アセンブラ

 Moscoviumと同様に、SamariumのアセンブラをOlive+のスクリプトで提供します。以下リンクより、アクセスしてください。

  Samariumアセンブラマニュアル

 以降、具体例を交えながら、アセンブラに備わった機能を解説してみます。

プログラミングスタイル

 スタックマシンによる処理は概ね、以下の様な流れで実行されます。

 この様な、スタックマシン特有のプログラミングスタイルに合ったアセンブラ記法を採用しています。

定数をスタックへ積む

 push疑似命令を使って16ビット定数をスタックへ積むことができます。

  例)push 0x8765

 アセンブラは、その他の符号化即値命令や演算命令等を活用して、なるべく短くて速い命令列へ自動的に展開します。

  例)push −16

  例)push 0xff00

  例)push 0x4000

 最短で1命令、最長でも3命令で、16ビット定数をスタックへ積むことが可能です。
 定数値によって1命令〜3命令に変化する事が好ましくない場合には、pushf疑似命令を使う事で、常に3命令に固定する事が可能です。

CPU命令

 add,and,jmp等の命令は、そのまま記述可能です。

  例)

引数をスタックへ積んでから、命令を実行

 各命令の引数として、カンマ(,)で区切ったデータを渡すことができます。この時、逆ポーランド記法との違いには留意してください。

  例)記法の違い

 引数は左から順にスタックへ積むので、記述する順番に注意が必要です。上例の通り、アセンブラとしての記法は逆ポーランド記法と異なりますが、引数(オペランド)等のスタック操作に関しては逆ポーランド記法に準じています。

※アセンブラでは、引数が定数なのか/命令なのかを明示的に解釈可能な様に、命令が先頭に来る記法を採用しています。

  例)sub 100,10

 引数は、在っても無くても良いし、命令が必要とする引数の数と合わなくても構いません。

  例)add 1,2,3


 カンマで区切った引数リストに空白を挿入する事はできません。

  例)

  例)

間接値をスタックへ積む

 角括弧[ ]で括った引数は間接値として処理します。間接値とは、[ ]内の値をメモリ空間のアドレスと見て、そのアドレスから読み出した16ビットワードデータを指します。値その物を指していた、[ ]なしの直接値と区別して理解してください。
 間接値は、具体的にはldw命令に展開されます。

  例)add [dat1],[dat2]

 ただし、上例の通り複数命令に展開されて実行されるので、「人間がプログラミングし易い」以外の利点は有りません。

 間接値[A]と、直接値Aの違いを理解して、正しく使い分けてください。

 ですので、以下例のごとく解釈されます。

  例)add A,2

  例)

サブルーチン分岐

 サブルーチンの先頭アドレスにアドレスラベルを設定して、サブルーチン呼び出しを実行するのは、Moscovium等と同じです。

  例)サブルーチン定義


 サブルーチンmuluへ分岐する命令はjal命令です。引数A,Bをスタックへ積んでからサブルーチン分岐するには、以下の様に記述できます。

  例)サブルーチン分岐


 これを以下の様に略記可能です。

  例)サブルーチン呼び出し(短縮書式)


 サブルーチン先頭のアドレスラベルmuluにアスタリスク(*)を付加した短縮書式です。

 乗算命令を持たないSamariumなのに、まるで、乗算命令(mulu*)を持っているかのような記述が可能となる事に注目してください。除算ルーチン(divu*)も作って四則演算を揃えたり、32ビット演算ルーチン(addl*やsubl*、stl*、ldl*等)を揃えて32ビット演算させてみたりと、マクロアセンブラの様な使い勝手を提供します。

  例)四則演算


 命令セットアーキテクチャから、一気にアセンブラの機能設計まで完了してしまいました。この辺りを考える時が一番楽しい所なんですよ。ここから先の詳細設計では、頭を悩ます場面が沢山待ち構えていますから。


btn_arduino.png

btn_prev.png btn_next.png

home