4番目のCPU。 これは何ですか (パート1)

HabréにはFortについての投稿はほとんどありませんでしたが、最近のムーアの素晴らしい作品に感銘を受けました。別のフォートプロセッサJ1について説明します。

これはおそらく、実際に使用できる最小のプロセッサです。

FPGAでそれを自分で行うことができますが、電子機器から遠く離れた人として、私は彼のエミュレータを書きます。 そして、投稿に異常を追加するために、Goでエミュレーターを作成します。



ハードウェア機能


プロセッサは16ビットで、最大32 KBのメモリをアドレス指定できます(以下の64 KBを理解できない理由)。 コードとデータは異なるメモリ領域にあり、このプロセッサで実行されるプログラムの最大サイズも最大32 KBです。



アーキテクチャはスタックされ、プロセッサには単一のレジスタが含まれていません(明示的にアクセスできます)。 ただし、2つのスタックが含まれます。



-データスタック-コンピューティング用に設計

-呼び出しスタック-関数を呼び出すときに戻りアドレスを保存するように設計されています。 一時的なストレージとして使用されることもあります。



両方のスタックは浅く、それぞれ33要素です。 もちろん、スタックも16ビットです。



以上です。 I / Oポートの代わりに、メモリマップドI / Oを使用することが提案されています。 このため、上位16Kbのメモリを予約することをお勧めします。



エミュレーター


Goでイデオロギー的に正しいコードのふりをするつもりはありませんが、言語に詳しくない人でもそれを明確にしようと思います。



プロセッサ構造について説明します。



 type J1Stack [33]uint16 type J1Cpu struct { pc uint16 r J1Stack s J1Stack rp int sp int mem []uint16 }
      
      







合計すると、内部のエミュレートされたプロセッサーには、pc命令カウンター、データスタックs、コールスタックr、これらのスタックの最上部へのポインターspとrp、および内部メモリがあります(ところで、バイトにアクセスできないため、メモリも16ビットのみです) 。



J1Cpu型のコンストラクター関数を作成します。



 func NewJ1Cpu(memSize int) (j1 *J1Cpu) { j1 = new(J1Cpu) j1.mem = make([]uint16, memSize) j1.sp, j1.rp = -1, -1 return j1 }
      
      







コンストラクターで、指定された量のメモリを割り当て、スタックを初期化します。



説明書


プロセッサには5つの命令があります。





次のように機能します。

LIT X:値Xをスタックsの上に置きます。1番目のビットはそれがリテラルであることを示しているため、15ビットのみが値に割り当てられます。 したがって、コードとデータの15ビットアドレス指定。

JMP X:アドレスXに移動

JZ X:値0がスタックsの先頭にある場合、アドレスXに移動します

CALL X:スタックrの先頭に値PC + 1(次の命令)を置き、アドレスXに移動します。

ALU:ここでは少し複雑です。以下で説明します。



これらの命令の実行をExecメソッドに実装します。



 const ( J1_OP = (7 << 13) J1_ARG = 0x1fff J1_JMP = (0 << 13) J1_JZ = (1 << 13) J1_CALL = (2 << 13) J1_ALU = (3 << 13) J1_LIT = (4 << 13) J1_LIT_MASK = 0x7fff ) func (j1 *J1Cpu) Exec(op uint16) { if op & J1_LIT != 0 { j1.sp++ j1.s[j1.sp] = op & J1_LIT_MASK j1.pc++ return } arg := op & J1_ARG switch op & J1_OP { case J1_JMP: j1.pc = arg case J1_JZ: if j1.s[j1.sp] == 0 { j1.pc = arg } j1.sp-- case J1_CALL: j1.rp++ j1.r[j1.rp] = j1.pc + 1 j1.pc = arg case J1_ALU: j1.execAlu(arg) } }
      
      







スタックの最上部へのポインターを変更することにより、プリインクリメントとポストデクリメントを使用します。 ただし、Goでは、Cのように使用することはできません(stack [++ sp] = x; ... x = stack [sp--])。 そのようなことを明示的に指定する必要があるからです。



execAluメソッドを実装するために残ります-そして、仕事は完了です!



J1 ALU


これは最も強力な命令です。 まず、プロセッサの作成者が使用する文字を決定しましょう。



T-命令の実行開始時のスタックの一番上の要素

Nは、命令の先頭でTに続く要素です

T 'は算術演算の結果です

Rは、命令の実行開始時の呼び出しスタックの最上位にある要素です

PC-プロセッサー命令カウンター値

[T]-アドレスTのメモリへの間接アクセス



16の算術命令のみがあります。

0:T '= T

1:T '= N

2:T '= T + N

3:T '= TおよびN

4:T '= TまたはN

5:T '= T xor N

6:T '=〜T(ビットごとの反転)

7:T '=(T == N)

8:T '=(N <T)

9:T '= N >> T(Tビットだけ右にシフト)

10:T '= T-1

11:T '= R

12:T '= [T]

13:T '= N << T

14:T '=深度(データスタックの深度)

15:T '=(N <T)、今回は符号なし比較



これらの操作の実装は次のようになります。

 func (j1 *J1Cpu) execAlu(op uint16) { var res, t, n, r uint16 if j1.sp >= 0 { t = j1.s[j1.sp] } if j1.sp > 0 { n = j1.s[j1.sp-1] } if j1.rp > 0 { r = j1.r[j1.rp-1] } j1.pc++ code := (op & (0xf << 8)) >> 8 switch code { case 0: res = t case 1: res = n case 2: res = t + n case 3: res = t & n case 4: res = t | n case 5: res = t ^ n case 6: res = ^t case 7: if n == t { res = 1 } case 8: if int16(n) < int16(t) { res = 1 } case 9: res = n >> t case 10: res = t - 1 case 11: res = j1.r[r] case 12: res = j1.mem[t] case 13: res = n << t case 14: res = uint16(j1.sp + 1) case 15: if n < t { res = 1 } } ds := op & 3 rs := (op & (3 << 2)) >> 2 if ds == 1 { j1.sp++ } if ds == 2 { j1.sp-- } if rs == 1 { j1.rp++ } if rs == 2 { j1.rp-- } if op&(1<<5) != 0 { println("N->[T]") j1.mem[t] = n } if op&(1<<6) != 0 { println("T->R") if j1.rp < 0 { panic("Call stack underrun") } j1.r[j1.rp] = t } if op&(1<<7) != 0 { println("T->N") if j1.sp > 0 { j1.s[j1.sp-1] = t } } if op&(1<<12) != 0 { println("R->PC") j1.pc = j1.r[r] } if j1.sp >= 0 { j1.s[j1.sp] = res } }
      
      







コマンドのパラメーターをあまり最適に解析しないため、関数は非常に大きいことが判明しました。 一般的には、T、N、Rの現在の値を受け取り、T 'の新しい値を計算し、スタックの最上部にポインターを移動し、Tの新しい値を設定します。

これが、プロセッサが実行できることのすべてです。



たとえば、2つの数字を追加すると3つのコマンドになります

LIT 5(スタック上-5)

LIT 7(スタック上-5 7)

ALU OP = 2(追加)、DS = -1(つまり、スタックポインターを1ずつ追加および削減します。スタック上で12)。



興味深いことに、関数を終了するために個別の命令は必要ありません。 最後の命令で、フラグ「R-> PC」が追加されます。 開発者によると、これによりコード量が約7%削減されます。



フォースは実際どこにいるのでしょうか?


実際、この一連の命令は、Fort言語専用に最適化されています。 この言語のほとんどの単語は、単一の命令J1に対応しています。 しかし、J1プロセッサーに関するFortの簡単な紹介は次の記事にありますそして、エミュレーターの完全なソースコードへのリンクを残します

誰がすでに砦を知っているのか-J1のクロスコンパイラはこちら (crossj1.fs)にあります。



All Articles